diff options
author | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2020-07-20 15:45:24 +0100 |
---|---|---|
committer | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2020-07-20 19:49:04 +0100 |
commit | 2440c68b33ab50ed8fc15aa65ad98ad5bb088000 (patch) | |
tree | 5aa3a7b8c4d7d5c470c8c09119a9b749853d8fc5 /src | |
parent | eecdc1f7dea3739f3a1442d262eb46feabd805fd (diff) | |
download | rspamd-2440c68b33ab50ed8fc15aa65ad98ad5bb088000.tar.gz rspamd-2440c68b33ab50ed8fc15aa65ad98ad5bb088000.zip |
[Rework] Rework and refactor forged recipients plugin
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/lua/forged_recipients.lua | 138 |
1 files 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) |