commit 228161c: [Feature] Add BOUNCE rule
Anton Yuzhaninov
citrin+git at citrin.ru
Mon Aug 10 19:28:06 UTC 2020
Author: Anton Yuzhaninov
Date: 2020-08-10 16:04:10 +0100
URL: https://github.com/rspamd/rspamd/commit/228161c45cb2443ff942dd5886a82e43862fa9d3 (refs/pull/3468/head)
[Feature] Add BOUNCE rule
---
conf/scores.d/headers_group.conf | 4 ++
rules/bounce.lua | 117 +++++++++++++++++++++++++++++++++++++++
rules/rspamd.lua | 3 +-
3 files changed, 123 insertions(+), 1 deletion(-)
diff --git a/conf/scores.d/headers_group.conf b/conf/scores.d/headers_group.conf
index c82c3a752..83048ea28 100644
--- a/conf/scores.d/headers_group.conf
+++ b/conf/scores.d/headers_group.conf
@@ -68,4 +68,8 @@ symbols = {
weight = -0.2;
description = "Message seems to be from maillist";
}
+ "BOUNCE" {
+ weight = -0.1;
+ description = "(Non) Delivery Status Notification";
+ }
}
diff --git a/rules/bounce.lua b/rules/bounce.lua
new file mode 100644
index 000000000..21c0d3fe0
--- /dev/null
+++ b/rules/bounce.lua
@@ -0,0 +1,117 @@
+--[[
+Copyright (c) 2020, Anton Yuzhaninov <citrin at citrin.ru>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+]]--
+
+-- Rule to detect bounces:
+-- RFC 3464 Delivery status notifications and most common non-standard ones
+
+local function make_subj_bounce_keywords_re()
+ -- Words and phrases commonly used in Subjects for bounces
+ -- We cannot practically test all localized Subjects, but luckily English is by far the most common here
+ local keywords = {
+ 'could not send message',
+ "couldn't be delivered",
+ 'delivery failed',
+ 'delivery failure',
+ 'delivery report',
+ 'delivery warning',
+ 'failure delivery',
+ 'failure notice',
+ "hasn't been delivered",
+ 'mail failure',
+ 'returned mail',
+ 'undeliverable',
+ 'undelivered',
+ }
+ return string.format([[Subject=/\b(%s)\b/i{header}]], table.concat(keywords, '|'))
+end
+
+config.regexp.SUBJ_BOUNCE_WORDS = {
+ re = make_subj_bounce_keywords_re(),
+ group = 'headers',
+ score = 0.0,
+ description = 'Words/phrases typical for DNS'
+}
+
+rspamd_config.BOUNCE = {
+ callback = function(task)
+ local from = task:get_from('smtp')
+ if from and from[1].addr ~= '' then
+ -- RFC 3464:
+ -- Whenever an SMTP transaction is used to send a DSN, the MAIL FROM
+ -- command MUST use a NULL return address, i.e., "MAIL FROM:<>"
+ -- In practise it is almost always the case for DNS
+ return false
+ end
+
+
+ local parts = task:get_parts()
+ local top_type, top_subtype, params = parts[1]:get_type_full()
+ -- RFC 3464, RFC 8098
+ if top_type == 'multipart' and top_subtype == 'report' and params and
+ (params['report-type'] == 'delivery-status' or params['report-type'] == 'disposition-notification') then
+ -- Assume that inner parts are OK, don't check them to save time
+ return true, 1.0, 'DSN'
+ end
+
+ -- Apply heuristics for non-standard bounecs
+ local bounce_sender
+ local mime_from = task:get_from('mime')
+ if mime_from then
+ local from_user = mime_from[1].user:lower()
+ -- Check common bounce senders
+ if (from_user == 'postmaster' or from_user == 'mailer-daemon') then
+ bounce_sender = from_user
+ -- MDaemon >= 14.5 sends multipart/report (RFC 3464) DNS covered above,
+ -- but older versions send non-standard bounces with localized subjects and they
+ -- are still around
+ elseif from_user == 'mdaemon' and task:has_header('X-MDDSN-Message') then
+ return true, 1.0, 'MDaemon'
+ end
+ end
+
+ local subj_keywords = task:has_symbol('SUBJ_BOUNCE_WORDS')
+
+ if not (bounce_sender or subj_keywords) then
+ return false
+ end
+
+ if bounce_sender and subj_keywords then
+ return true, 0.5, bounce_sender .. '+subj'
+ end
+
+ -- Look for a message/rfc822(-headers) part inside
+ local rfc822_part
+ parts[10] = nil -- limit numbe of parts to check
+ for _, p in ipairs(parts) do
+ local mime_type, mime_subtype = p:get_type()
+ if (mime_subtype == 'rfc822' or mime_subtype == 'rfc822-headers') and
+ (mime_type == 'message' or mime_type == 'text') then
+ rfc822_part = mime_type .. '/' .. mime_subtype
+ break
+ end
+ end
+
+ if rfc822_part and bounce_sender then
+ return true, 0.5, bounce_sender .. '+' .. rfc822_part
+ elseif rfc822_part and subj_keywords then
+ return true, 0.2, rfc822_part .. '+subj'
+ end
+ end,
+ description = '(Non) Delivery Status Notification',
+ group = 'headers',
+}
+
+rspamd_config:register_dependency('BOUNCE', 'SUBJ_BOUNCE_WORDS')
diff --git a/rules/rspamd.lua b/rules/rspamd.lua
index a5dbef42d..64aefa9d1 100644
--- a/rules/rspamd.lua
+++ b/rules/rspamd.lua
@@ -37,6 +37,7 @@ dofile(local_rules .. '/http_headers.lua')
dofile(local_rules .. '/forwarding.lua')
dofile(local_rules .. '/mid.lua')
dofile(local_rules .. '/bitcoin.lua')
+dofile(local_rules .. '/bounce.lua')
dofile(local_rules .. '/content.lua')
dofile(local_rules .. '/controller/init.lua')
@@ -65,4 +66,4 @@ if rmaps and type(rmaps) == 'table' then
rspamd_maps[k] = map_or_err
end
end
-end
\ No newline at end of file
+end
More information about the Commits
mailing list