1
0
Fork 0
mirror of https://github.com/processone/ejabberd synced 2025-10-03 09:49:18 +02:00

mod_antispam: add format instructions

This commit is contained in:
Stefan Strigler 2025-06-05 14:31:02 +02:00
parent 639147be41
commit 34b40aec66
3 changed files with 675 additions and 519 deletions

File diff suppressed because it is too large Load diff

View file

@ -38,11 +38,15 @@
subscribe/3, subscribe/3,
unsubscribe/3]). unsubscribe/3]).
%% @format-begin
subscribe(RTBLHost, RTBLDomainsNode, From) -> subscribe(RTBLHost, RTBLDomainsNode, From) ->
FromJID = service_jid(From), FromJID = service_jid(From),
SubIQ = #iq{type = set, to = jid:make(RTBLHost), from = FromJID, SubIQ =
sub_els = [ #iq{type = set,
#pubsub{subscribe = #ps_subscribe{jid = FromJID, node = RTBLDomainsNode}}]}, to = jid:make(RTBLHost),
from = FromJID,
sub_els = [#pubsub{subscribe = #ps_subscribe{jid = FromJID, node = RTBLDomainsNode}}]},
?DEBUG("Sending subscription request:~n~p", [xmpp:encode(SubIQ)]), ?DEBUG("Sending subscription request:~n~p", [xmpp:encode(SubIQ)]),
ejabberd_router:route_iq(SubIQ, subscribe_result, self()). ejabberd_router:route_iq(SubIQ, subscribe_result, self()).
@ -51,19 +55,22 @@ unsubscribe(none, _PSNode, _From) ->
ok; ok;
unsubscribe(RTBLHost, RTBLDomainsNode, From) -> unsubscribe(RTBLHost, RTBLDomainsNode, From) ->
FromJID = jid:make(From), FromJID = jid:make(From),
SubIQ = #iq{type = set, to = jid:make(RTBLHost), from = FromJID, SubIQ =
sub_els = [ #iq{type = set,
#pubsub{unsubscribe = #ps_unsubscribe{jid = FromJID, node = RTBLDomainsNode}}]}, to = jid:make(RTBLHost),
from = FromJID,
sub_els =
[#pubsub{unsubscribe = #ps_unsubscribe{jid = FromJID, node = RTBLDomainsNode}}]},
ejabberd_router:route_iq(SubIQ, unsubscribe_result, self()). ejabberd_router:route_iq(SubIQ, unsubscribe_result, self()).
-spec request_blocked_domains(binary() | none, binary(), binary()) -> ok. -spec request_blocked_domains(binary() | none, binary(), binary()) -> ok.
request_blocked_domains(none, _PSNode, _From) -> request_blocked_domains(none, _PSNode, _From) ->
ok; ok;
request_blocked_domains(RTBLHost, RTBLDomainsNode, From) -> request_blocked_domains(RTBLHost, RTBLDomainsNode, From) ->
IQ = #iq{type = get, from = jid:make(From), IQ = #iq{type = get,
to = jid:make(RTBLHost), from = jid:make(From),
sub_els = [ to = jid:make(RTBLHost),
#pubsub{items = #ps_items{node = RTBLDomainsNode}}]}, sub_els = [#pubsub{items = #ps_items{node = RTBLDomainsNode}}]},
?DEBUG("Requesting RTBL blocked domains from ~s:~n~p", [RTBLHost, xmpp:encode(IQ)]), ?DEBUG("Requesting RTBL blocked domains from ~s:~n~p", [RTBLHost, xmpp:encode(IQ)]),
ejabberd_router:route_iq(IQ, blocked_domains, self()). ejabberd_router:route_iq(IQ, blocked_domains, self()).
@ -72,31 +79,35 @@ parse_blocked_domains(#iq{to = #jid{lserver = LServer}, type = result} = IQ) ->
?DEBUG("parsing iq-result items: ~p", [IQ]), ?DEBUG("parsing iq-result items: ~p", [IQ]),
RTBLDomainsNode = gen_mod:get_module_opt(LServer, ?SERVICE_MODULE, rtbl_domains_node), RTBLDomainsNode = gen_mod:get_module_opt(LServer, ?SERVICE_MODULE, rtbl_domains_node),
case xmpp:get_subtag(IQ, #pubsub{}) of case xmpp:get_subtag(IQ, #pubsub{}) of
#pubsub{items = #ps_items{node = RTBLDomainsNode, items = Items}} -> #pubsub{items = #ps_items{node = RTBLDomainsNode, items = Items}} ->
?DEBUG("Got items:~n~p", [Items]), ?DEBUG("Got items:~n~p", [Items]),
parse_items(Items); parse_items(Items);
_ -> _ ->
undefined undefined
end. end.
-spec parse_pubsub_event(stanza()) -> #{binary() => any()}. -spec parse_pubsub_event(stanza()) -> #{binary() => any()}.
parse_pubsub_event(#message{to = #jid{lserver = LServer}} = Msg) -> parse_pubsub_event(#message{to = #jid{lserver = LServer}} = Msg) ->
RTBLDomainsNode = gen_mod:get_module_opt(LServer, ?SERVICE_MODULE, rtbl_domains_node), RTBLDomainsNode = gen_mod:get_module_opt(LServer, ?SERVICE_MODULE, rtbl_domains_node),
case xmpp:get_subtag(Msg, #ps_event{}) of case xmpp:get_subtag(Msg, #ps_event{}) of
#ps_event{items = #ps_items{node = RTBLDomainsNode, items = Items, retract = RetractIds}} -> #ps_event{items =
maps:merge(retract_items(RetractIds), parse_items(Items)); #ps_items{node = RTBLDomainsNode,
Other -> items = Items,
?WARNING_MSG("Couldn't extract items: ~p", [Other]), retract = RetractIds}} ->
#{} maps:merge(retract_items(RetractIds), parse_items(Items));
Other ->
?WARNING_MSG("Couldn't extract items: ~p", [Other]),
#{}
end. end.
-spec parse_items([ps_item()]) -> #{binary() => any()}. -spec parse_items([ps_item()]) -> #{binary() => any()}.
parse_items(Items) -> parse_items(Items) ->
lists:foldl( lists:foldl(fun(#ps_item{id = ID}, Acc) ->
fun(#ps_item{id = ID}, Acc) -> %% TODO extract meta/extra instructions
%% TODO extract meta/extra instructions maps:put(ID, true, Acc)
maps:put(ID, true, Acc) end,
end, #{}, Items). #{},
Items).
-spec retract_items([binary()]) -> #{binary() => false}. -spec retract_items([binary()]) -> #{binary() => false}.
retract_items(Ids) -> retract_items(Ids) ->
@ -112,20 +123,22 @@ service_jid(Host) ->
-spec pubsub_event_handler(stanza()) -> drop | stanza(). -spec pubsub_event_handler(stanza()) -> drop | stanza().
pubsub_event_handler(#message{from = FromJid, pubsub_event_handler(#message{from = FromJid,
to = #jid{lserver = LServer, to =
lresource = <<?SERVICE_JID_PREFIX, _/binary>>}} = Msg) -> #jid{lserver = LServer,
lresource = <<?SERVICE_JID_PREFIX, _/binary>>}} =
Msg) ->
?DEBUG("Got RTBL message:~n~p", [Msg]), ?DEBUG("Got RTBL message:~n~p", [Msg]),
From = jid:encode(FromJid), From = jid:encode(FromJid),
case gen_mod:get_module_opt(LServer, ?SERVICE_MODULE, rtbl_host) of case gen_mod:get_module_opt(LServer, ?SERVICE_MODULE, rtbl_host) of
From -> From ->
ParsedItems = parse_pubsub_event(Msg), ParsedItems = parse_pubsub_event(Msg),
Proc = gen_mod:get_module_proc(LServer, ?SERVICE_MODULE), Proc = gen_mod:get_module_proc(LServer, ?SERVICE_MODULE),
gen_server:cast(Proc, {update_blocked_domains, ParsedItems}), gen_server:cast(Proc, {update_blocked_domains, ParsedItems}),
%% FIXME what's the difference between `{drop, ...}` and `{stop, {drop, ...}}`? %% FIXME what's the difference between `{drop, ...}` and `{stop, {drop, ...}}`?
drop; drop;
_Other -> _Other ->
?INFO_MSG("Got unexpected message from ~s to rtbl resource:~n~p", [From, Msg]), ?INFO_MSG("Got unexpected message from ~s to rtbl resource:~n~p", [From, Msg]),
Msg Msg
end; end;
pubsub_event_handler(Acc) -> pubsub_event_handler(Acc) ->
?DEBUG("unexpected something on pubsub_event_handler: ~p", [Acc]), ?DEBUG("unexpected something on pubsub_event_handler: ~p", [Acc]),

View file

@ -31,6 +31,8 @@
my_muc_jid/1, get_features/2, set_opt/3]). my_muc_jid/1, get_features/2, set_opt/3]).
-include("suite.hrl"). -include("suite.hrl").
%% @format-begin
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
@ -38,35 +40,51 @@
%%% Single tests %%% Single tests
%%%=================================================================== %%%===================================================================
single_cases() -> single_cases() ->
{antispam_single, [sequence], {antispam_single,
[single_test(spam_files), [sequence],
single_test(blocked_domains), [single_test(spam_files),
single_test(jid_cache), single_test(blocked_domains),
single_test(rtbl_domains)]}. single_test(jid_cache),
single_test(rtbl_domains)]}.
spam_files(Config) -> spam_files(Config) ->
Host = ?config(server, Config), Host = ?config(server, Config),
To = my_jid(Config), To = my_jid(Config),
SpamJID = jid:make(<<"spammer_jid">>, <<"localhost">>, <<"spam_client">>), SpamJID = jid:make(<<"spammer_jid">>, <<"localhost">>, <<"spam_client">>),
SpamJIDMsg = #message{from = SpamJID, to = To, type = chat, body = [#text{data = <<"hello world">>}]}, SpamJIDMsg =
#message{from = SpamJID,
to = To,
type = chat,
body = [#text{data = <<"hello world">>}]},
is_spam(SpamJIDMsg), is_spam(SpamJIDMsg),
Spammer = jid:make(<<"spammer">>, <<"localhost">>, <<"spam_client">>), Spammer = jid:make(<<"spammer">>, <<"localhost">>, <<"spam_client">>),
NoSpamMsg = #message{from = Spammer, to = To, type = chat, body = [#text{data = <<"hello world">>}]}, NoSpamMsg =
#message{from = Spammer,
to = To,
type = chat,
body = [#text{data = <<"hello world">>}]},
is_not_spam(NoSpamMsg), is_not_spam(NoSpamMsg),
SpamMsg = #message{from = Spammer, to = To, type = chat, body = [#text{data = <<"hello world\nhttps://spam.domain.url">>}]}, SpamMsg =
#message{from = Spammer,
to = To,
type = chat,
body = [#text{data = <<"hello world\nhttps://spam.domain.url">>}]},
is_spam(SpamMsg), is_spam(SpamMsg),
%% now check this mischief is in jid_cache %% now check this mischief is in jid_cache
is_spam(NoSpamMsg), is_spam(NoSpamMsg),
mod_antispam:drop_from_spam_filter_cache(Host, jid:to_string(Spammer)), mod_antispam:drop_from_spam_filter_cache(Host, jid:to_string(Spammer)),
is_not_spam(NoSpamMsg), is_not_spam(NoSpamMsg),
?retry(100, 10, ?retry(100, 10, ?match(true, (has_spam_domain(<<"spam_domain.org">>))(Host))),
?match(true, (has_spam_domain(<<"spam_domain.org">>))(Host))),
SpamDomain = jid:make(<<"spammer">>, <<"spam_domain.org">>, <<"spam_client">>), SpamDomain = jid:make(<<"spammer">>, <<"spam_domain.org">>, <<"spam_client">>),
SpamDomainMsg = #message{from = SpamDomain, to = To, type = chat, body = [#text{data = <<"hello world">>}]}, SpamDomainMsg =
#message{from = SpamDomain,
to = To,
type = chat,
body = [#text{data = <<"hello world">>}]},
is_spam(SpamDomainMsg), is_spam(SpamDomainMsg),
?match({ok, _}, mod_antispam:remove_blocked_domain(Host, <<"spam_domain.org">>)), ?match({ok, _}, mod_antispam:remove_blocked_domain(Host, <<"spam_domain.org">>)),
?match([], mod_antispam:get_blocked_domains(Host)), ?match([], mod_antispam:get_blocked_domains(Host)),
@ -78,9 +96,12 @@ blocked_domains(Config) ->
?match([], mod_antispam:get_blocked_domains(Host)), ?match([], mod_antispam:get_blocked_domains(Host)),
SpamFrom = jid:make(<<"spammer">>, <<"spam.domain">>, <<"spam_client">>), SpamFrom = jid:make(<<"spammer">>, <<"spam.domain">>, <<"spam_client">>),
To = my_jid(Config), To = my_jid(Config),
Msg = #message{from = SpamFrom, to = To, type = chat, body = [#text{data = <<"hello world">>}]}, Msg = #message{from = SpamFrom,
to = To,
type = chat,
body = [#text{data = <<"hello world">>}]},
is_not_spam(Msg), is_not_spam(Msg),
?match({ok, _}, mod_antispam:add_blocked_domain(<<"global">>, <<"spam.domain">>)), ?match({ok, _}, mod_antispam:add_blocked_domain(<<"global">>, <<"spam.domain">>)),
is_spam(Msg), is_spam(Msg),
Vhosts = [H || H <- ejabberd_option:hosts(), gen_mod:is_loaded(H, mod_antispam)], Vhosts = [H || H <- ejabberd_option:hosts(), gen_mod:is_loaded(H, mod_antispam)],
NumVhosts = length(Vhosts), NumVhosts = length(Vhosts),
@ -102,7 +123,10 @@ jid_cache(Config) ->
Host = ?config(server, Config), Host = ?config(server, Config),
SpamFrom = jid:make(<<"spammer">>, Host, <<"spam_client">>), SpamFrom = jid:make(<<"spammer">>, Host, <<"spam_client">>),
To = my_jid(Config), To = my_jid(Config),
Msg = #message{from = SpamFrom, to = To, type = chat, body = [#text{data = <<"hello world">>}]}, Msg = #message{from = SpamFrom,
to = To,
type = chat,
body = [#text{data = <<"hello world">>}]},
is_not_spam(Msg), is_not_spam(Msg),
mod_antispam:add_to_spam_filter_cache(Host, jid:to_string(SpamFrom)), mod_antispam:add_to_spam_filter_cache(Host, jid:to_string(SpamFrom)),
is_spam(Msg), is_spam(Msg),
@ -112,25 +136,45 @@ jid_cache(Config) ->
rtbl_domains(Config) -> rtbl_domains(Config) ->
Host = ?config(server, Config), Host = ?config(server, Config),
RTBLHost = jid:to_string(suite:pubsub_jid(Config)), RTBLHost =
jid:to_string(
suite:pubsub_jid(Config)),
RTBLDomainsNode = <<"spam_source_domains">>, RTBLDomainsNode = <<"spam_source_domains">>,
OldOpts = gen_mod:get_module_opts(Host, mod_antispam), OldOpts = gen_mod:get_module_opts(Host, mod_antispam),
NewOpts = maps:merge(OldOpts, #{rtbl_host => RTBLHost, rtbl_domains_node => RTBLDomainsNode}), NewOpts =
maps:merge(OldOpts, #{rtbl_host => RTBLHost, rtbl_domains_node => RTBLDomainsNode}),
Owner = jid:make(?config(user, Config), ?config(server, Config), <<>>), Owner = jid:make(?config(user, Config), ?config(server, Config), <<>>),
{result, _} = mod_pubsub:create_node(RTBLHost, ?config(server, Config), RTBLDomainsNode, Owner, <<"flat">>), {result, _} =
{result, _} = mod_pubsub:publish_item(RTBLHost, ?config(server, Config), RTBLDomainsNode, Owner, <<"spam.source.domain">>, mod_pubsub:create_node(RTBLHost,
[xmpp:encode(#ps_item{id = <<"spam.source.domain">>, sub_els = []})]), ?config(server, Config),
RTBLDomainsNode,
Owner,
<<"flat">>),
{result, _} =
mod_pubsub:publish_item(RTBLHost,
?config(server, Config),
RTBLDomainsNode,
Owner,
<<"spam.source.domain">>,
[xmpp:encode(#ps_item{id = <<"spam.source.domain">>,
sub_els = []})]),
mod_antispam:reload(Host, OldOpts, NewOpts), mod_antispam:reload(Host, OldOpts, NewOpts),
?match({ok, _}, mod_antispam:remove_blocked_domain(Host, <<"spam_domain.org">>)), ?match({ok, _}, mod_antispam:remove_blocked_domain(Host, <<"spam_domain.org">>)),
?retry(100, 10, ?retry(100,
10,
?match([<<"spam.source.domain">>], mod_antispam:get_blocked_domains(Host))), ?match([<<"spam.source.domain">>], mod_antispam:get_blocked_domains(Host))),
{result, _} = mod_pubsub:publish_item(RTBLHost, ?config(server, Config), RTBLDomainsNode, Owner, <<"spam.source.another">>, {result, _} =
[xmpp:encode(#ps_item{id = <<"spam.source.another">>, sub_els = []})]), mod_pubsub:publish_item(RTBLHost,
?retry(100, 10, ?config(server, Config),
?match(true, (has_spam_domain(<<"spam.source.another">>))(Host))), RTBLDomainsNode,
{result, _} = mod_pubsub:delete_item(RTBLHost, RTBLDomainsNode, Owner, <<"spam.source.another">>, true), Owner,
?retry(100, 10, <<"spam.source.another">>,
?match(false, (has_spam_domain(<<"spam.source.another">>))(Host))), [xmpp:encode(#ps_item{id = <<"spam.source.another">>,
sub_els = []})]),
?retry(100, 10, ?match(true, (has_spam_domain(<<"spam.source.another">>))(Host))),
{result, _} =
mod_pubsub:delete_item(RTBLHost, RTBLDomainsNode, Owner, <<"spam.source.another">>, true),
?retry(100, 10, ?match(false, (has_spam_domain(<<"spam.source.another">>))(Host))),
{result, _} = mod_pubsub:delete_node(RTBLHost, RTBLDomainsNode, Owner), {result, _} = mod_pubsub:delete_node(RTBLHost, RTBLDomainsNode, Owner),
disconnect(Config). disconnect(Config).
@ -141,9 +185,7 @@ single_test(T) ->
list_to_atom("antispam_" ++ atom_to_list(T)). list_to_atom("antispam_" ++ atom_to_list(T)).
has_spam_domain(Domain) -> has_spam_domain(Domain) ->
fun(Host) -> fun(Host) -> lists:member(Domain, mod_antispam:get_blocked_domains(Host)) end.
lists:member(Domain, mod_antispam:get_blocked_domains(Host))
end.
is_not_spam(Msg) -> is_not_spam(Msg) ->
?match({Msg, undefined}, mod_antispam:s2s_receive_packet({Msg, undefined})). ?match({Msg, undefined}, mod_antispam:s2s_receive_packet({Msg, undefined})).