commit 2440c68: [Rework] Rework and refactor forged recipients plugin
Vsevolod Stakhov
vsevolod at highsecure.ru
Mon Jul 20 18:56:07 UTC 2020
Author: Vsevolod Stakhov
Date: 2020-07-20 15:45:24 +0100
URL: https://github.com/rspamd/rspamd/commit/2440c68b33ab50ed8fc15aa65ad98ad5bb088000 (HEAD -> master)
[Rework] Rework and refactor forged recipients plugin
---
src/plugins/lua/forged_recipients.lua | 138 ++++++++++++++++++++++------------
1 file changed, 92 insertions(+), 46 deletions(-)
diff --git a/src/plugins/lua/forged_recipients.lua b/src/plugins/lua/forged_recipients.lua
index 328644c8c..4f7942f79 100644
--- a/src/plugins/lua/forged_recipients.lua
+++ b/src/plugins/lua/forged_recipients.lua
@@ -18,7 +18,14 @@ limitations under the License.
-- in mime headers
if confighelp then
- return
+ rspamd_config:add_example(nil, 'forged_recipients',
+ "Check forged recipients and senders (e.g. mime and smtp recipients mismatch)",
+ [[
+ forged_recipients {
+ symbol_sender = "FORGED_SENDER"; # Symbol for a forged sender
+ symbol_rcpt = "FORGED_RECIPIENTS"; # Symbol for a forged recipients
+ }
+ ]])
end
local symbol_rcpt = 'FORGED_RECIPIENTS'
@@ -29,68 +36,107 @@ local E = {}
local function check_forged_headers(task)
local auser = task:get_user()
local delivered_to = task:get_header('Delivered-To')
- local smtp_rcpt = task:get_recipients(1)
+ local smtp_rcpts = task:get_recipients(1)
local smtp_from = task:get_from(1)
- local res
- local score = 1.0
- if not smtp_rcpt then return end
- if #smtp_rcpt == 0 then return end
+ if not smtp_rcpts then return end
+ if #smtp_rcpts == 0 then return end
- local mime_rcpt = task:get_recipients({'mime','orig'})
+ local mime_rcpts = task:get_recipients({ 'mime', 'orig'})
- if not mime_rcpt then
+ if not mime_rcpts then
return
- elseif #mime_rcpt == 0 then
+ elseif #mime_rcpts == 0 then
return
end
-- Find pair for each smtp recipient in To or Cc headers
- -- This cycle has O(N^2) complexity so it is better to limit number of iterations
- if #smtp_rcpt > 100 or #mime_rcpt > 100 then
+ if #smtp_rcpts > 100 or #mime_rcpts > 100 then
-- Trim array, suggested by Anton Yuzhaninov
- smtp_rcpt[100] = nil
- mime_rcpt[100] = nil
+ smtp_rcpts[100] = nil
+ mime_rcpts[100] = nil
end
- for _,sr in ipairs(smtp_rcpt) do
- res = false
- for _,mr in ipairs(mime_rcpt) do
- if mr.addr and mr.addr ~= '' then
- if sr['addr'] and
- string.lower(mr['addr']) == string.lower(sr['addr']) then
- res = true
- break
- elseif delivered_to and delivered_to == mr['addr'] then
- -- allow alias expansion and forwarding (Postfix)
- res = true
- break
- elseif auser and auser == sr['addr'] then
- -- allow user to BCC themselves
- res = true
- break
- elseif ((smtp_from or E)[1] or E).addr and
- smtp_from[1]['addr'] == sr['addr'] then
- -- allow sender to BCC themselves
- res = true
- break
- elseif mr['user'] and sr['user'] and
- string.lower(mr['user']) == string.lower(sr['user']) then
- -- If we have the same username but for another domain, then
- -- lower the overall score
- score = score / 2
- end
+ -- map smtp recipient domains to a list of addresses for this domain
+ local smtp_rcpt_domain_map = {}
+ local smtp_rcpt_map = {}
+ for _, smtp_rcpt in ipairs(smtp_rcpts) do
+ local addr = smtp_rcpt.addr
+
+ if addr and addr ~= '' then
+ local dom = string.lower(smtp_rcpt.domain)
+ addr = addr:lower()
+
+ local dom_map = smtp_rcpt_domain_map[dom]
+ if not dom_map then
+ dom_map = {}
+ smtp_rcpt_domain_map[dom] = dom_map
+ end
+
+ dom_map[addr] = smtp_rcpt
+ smtp_rcpt_map[addr] = smtp_rcpt
+
+ if auser and auser == addr then
+ smtp_rcpt.matched = true
+ end
+ if ((smtp_from or E)[1] or E).addr and
+ smtp_from[1]['addr'] == addr then
+ -- allow sender to BCC themselves
+ smtp_rcpt.matched = true
+ end
+ end
+ end
+
+ for _,mime_rcpt in ipairs(mime_rcpts) do
+ if mime_rcpt.addr and mime_rcpt.addr ~= '' then
+ local addr = string.lower(mime_rcpt.addr)
+ local dom = string.lower(mime_rcpt.domain)
+ local matched_smtp_addr = smtp_rcpt_map[addr]
+ if matched_smtp_addr then
+ -- Direct match, go forward
+ matched_smtp_addr.matched = true
+ mime_rcpt.matched = true
+ elseif delivered_to and delivered_to == addr then
+ mime_rcpt.matched = true
+ elseif auser and auser == addr then
+ -- allow user to BCC themselves
+ mime_rcpt.matched = true
else
- res = true
+ local matched_smtp_domain = smtp_rcpt_domain_map[dom]
+
+ if matched_smtp_domain then
+ -- Same domain but another user, it is likely okay due to aliases substitution
+ mime_rcpt.matched = true
+ -- Special field
+ matched_smtp_domain._seen_mime_domain = true
+ end
end
end
- if not res then
- local mra = mime_rcpt[1].addr .. (#mime_rcpt > 1 and ' ..' or '')
- local sra = smtp_rcpt[1].addr .. (#smtp_rcpt > 1 and ' ...' or '')
- task:insert_result(symbol_rcpt, score, mra, sra)
- break
+ end
+
+ -- Now go through all lists one more time and find unmatched stuff
+ local opts = {}
+ local seen_mime_unmatched = false
+ local seen_smtp_unmatched = false
+ for _,mime_rcpt in ipairs(mime_rcpts) do
+ if not mime_rcpt.matched then
+ seen_mime_unmatched = true
+ table.insert(opts, 'm:' .. mime_rcpt.addr)
+ end
+ end
+ for _,smtp_rcpt in ipairs(smtp_rcpts) do
+ if not smtp_rcpt.matched then
+ if not smtp_rcpt_domain_map[smtp_rcpt.domain]._seen_mime_domain then
+ seen_smtp_unmatched = true
+ table.insert(opts, 's:' .. smtp_rcpt.addr)
+ end
end
end
+
+ if seen_smtp_unmatched and seen_mime_unmatched then
+ task:insert_result(symbol_rcpt, 1.0, opts)
+ end
+
-- Check sender
if smtp_from and smtp_from[1] and smtp_from[1]['addr'] ~= '' then
local mime_from = task:get_from(2)
More information about the Commits
mailing list