123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- --[[
- 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 status',
- '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 DSN'
- }
-
- 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 DSN
- 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) DSN 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')
|