Merge branch 'feat/mrf-dnsrbl' into 'develop'
Anti-spam using an MRF DNSRBL See merge request pleroma/pleroma!3278
This commit is contained in:
commit
10713fa913
1
changelog.d/add-rbl-mrf.add
Normal file
1
changelog.d/add-rbl-mrf.add
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add DNSRBL MRF
|
@ -410,6 +410,11 @@ config :pleroma, :mrf_vocabulary,
|
|||||||
accept: [],
|
accept: [],
|
||||||
reject: []
|
reject: []
|
||||||
|
|
||||||
|
config :pleroma, :mrf_dnsrbl,
|
||||||
|
nameserver: "127.0.0.1",
|
||||||
|
port: 53,
|
||||||
|
zone: "bl.pleroma.com"
|
||||||
|
|
||||||
# threshold of 7 days
|
# threshold of 7 days
|
||||||
config :pleroma, :mrf_object_age,
|
config :pleroma, :mrf_object_age,
|
||||||
threshold: 604_800,
|
threshold: 604_800,
|
||||||
|
142
lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex
Normal file
142
lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
|
||||||
|
@moduledoc """
|
||||||
|
Dynamic activity filtering based on an RBL database
|
||||||
|
|
||||||
|
This MRF makes queries to a custom DNS server which will
|
||||||
|
respond with values indicating the classification of the domain
|
||||||
|
the activity originated from. This method has been widely used
|
||||||
|
in the email anti-spam industry for very fast reputation checks.
|
||||||
|
|
||||||
|
e.g., if the DNS response is 127.0.0.1 or empty, the domain is OK
|
||||||
|
Other values such as 127.0.0.2 may be used for specific classifications.
|
||||||
|
|
||||||
|
Information for why the host is blocked can be stored in a corresponding TXT record.
|
||||||
|
|
||||||
|
This method is fail-open so if the queries fail the activites are accepted.
|
||||||
|
|
||||||
|
An example of software meant for this purpsoe is rbldnsd which can be found
|
||||||
|
at http://www.corpit.ru/mjt/rbldnsd.html or mirrored at
|
||||||
|
https://git.pleroma.social/feld/rbldnsd
|
||||||
|
|
||||||
|
It is highly recommended that you run your own copy of rbldnsd and use an
|
||||||
|
external mechanism to sync/share the contents of the zone file. This is
|
||||||
|
important to keep the latency on the queries as low as possible and prevent
|
||||||
|
your DNS server from being attacked so it fails and content is permitted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@query_retries 1
|
||||||
|
@query_timeout 500
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"actor" => actor} = object) do
|
||||||
|
actor_info = URI.parse(actor)
|
||||||
|
|
||||||
|
with {:ok, object} <- check_rbl(actor_info, object) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
_ -> {:reject, "[DNSRBLPolicy]"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
mrf_dnsrbl =
|
||||||
|
Config.get(:mrf_dnsrbl)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_dnsrbl: mrf_dnsrbl}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_dnsrbl,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy",
|
||||||
|
label: "MRF DNSRBL",
|
||||||
|
description: "DNS RealTime Blackhole Policy",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :nameserver,
|
||||||
|
type: {:string},
|
||||||
|
description: "DNSRBL Nameserver to Query (IP or hostame)",
|
||||||
|
suggestions: ["127.0.0.1"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :port,
|
||||||
|
type: {:string},
|
||||||
|
description: "Nameserver port",
|
||||||
|
suggestions: ["53"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :zone,
|
||||||
|
type: {:string},
|
||||||
|
description: "Root zone for querying",
|
||||||
|
suggestions: ["bl.pleroma.com"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_rbl(%{host: actor_host}, object) do
|
||||||
|
with false <- match?(^actor_host, Pleroma.Web.Endpoint.host()),
|
||||||
|
zone when not is_nil(zone) <- Keyword.get(Config.get([:mrf_dnsrbl]), :zone) do
|
||||||
|
query =
|
||||||
|
Enum.join([actor_host, zone], ".")
|
||||||
|
|> String.to_charlist()
|
||||||
|
|
||||||
|
rbl_response = rblquery(query)
|
||||||
|
|
||||||
|
if Enum.empty?(rbl_response) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
Task.start(fn ->
|
||||||
|
reason = rblquery(query, :txt) || "undefined"
|
||||||
|
|
||||||
|
Logger.warning(
|
||||||
|
"DNSRBL Rejected activity from #{actor_host} for reason: #{inspect(reason)}"
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_ -> {:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_rblhost_ip(rblhost) do
|
||||||
|
case rblhost |> String.to_charlist() |> :inet_parse.address() do
|
||||||
|
{:ok, _} -> rblhost |> String.to_charlist() |> :inet_parse.address()
|
||||||
|
_ -> {:ok, rblhost |> String.to_charlist() |> :inet_res.lookup(:in, :a) |> Enum.random()}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rblquery(query, type \\ :a) do
|
||||||
|
config = Config.get([:mrf_dnsrbl])
|
||||||
|
|
||||||
|
case get_rblhost_ip(config[:nameserver]) do
|
||||||
|
{:ok, rblnsip} ->
|
||||||
|
:inet_res.lookup(query, :in, type,
|
||||||
|
nameservers: [{rblnsip, config[:port]}],
|
||||||
|
timeout: @query_timeout,
|
||||||
|
retry: @query_retries
|
||||||
|
)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user