mirror of
https://github.com/processone/ejabberd
synced 2025-10-03 17:59:31 +02:00
mod_antispam: add format instructions
This commit is contained in:
parent
639147be41
commit
34b40aec66
3 changed files with 675 additions and 519 deletions
1007
src/mod_antispam.erl
1007
src/mod_antispam.erl
File diff suppressed because it is too large
Load diff
|
@ -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]),
|
||||||
|
|
|
@ -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})).
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue