3 changed files with 123 additions and 1 deletions
@ -0,0 +1,117 @@ |
|||
--[[ |
|||
Copyright (c) 2020, Anton Yuzhaninov <citrin@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') |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue