From 427fe76ed30b13a398dbe147ef7ecdf5d126efc0 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Thu, 27 Apr 2017 17:51:11 +0100 Subject: [PATCH] [Fix] Fix couple of issues in FORWARDED rule --- rules/forwarding.lua | 232 +++++++++++++++++++++++-------------------- 1 file changed, 122 insertions(+), 110 deletions(-) diff --git a/rules/forwarding.lua b/rules/forwarding.lua index eae803f81..d145890d2 100644 --- a/rules/forwarding.lua +++ b/rules/forwarding.lua @@ -16,133 +16,145 @@ limitations under the License. -- Rules to detect forwarding +local fun = require "fun" + rspamd_config.FWD_GOOGLE = { - callback = function (task) - if not (task:has_from(1) and task:has_recipients(1)) then - return false - end - local envfrom = task:get_from(1) - local envrcpts = task:get_recipients(1) - -- Forwarding will only be to a single recipient - if #envrcpts > 1 then return false end - -- Get recipient and compute VERP address - local rcpt = envrcpts[1].addr:lower() - local verp = rcpt:gsub('@','=') - -- Get the user portion of the envfrom - local ef_user = envfrom[1].user:lower() - -- Check for a match - if ef_user:find('+caf_=' .. verp, 1, true) then - local _,_,user = ef_user:find('^(.+)+caf_=') - if user then - user = user .. '@' .. envfrom[1].domain - return true, user - end - end - return false - end, - score = 0.0, - description = "Message was forwarded by Google", - group = "forwarding" + callback = function (task) + if not (task:has_from(1) and task:has_recipients(1)) then + return false + end + local envfrom = task:get_from(1) + local envrcpts = task:get_recipients(1) + -- Forwarding will only be to a single recipient + if #envrcpts > 1 then return false end + -- Get recipient and compute VERP address + local rcpt = envrcpts[1].addr:lower() + local verp = rcpt:gsub('@','=') + -- Get the user portion of the envfrom + local ef_user = envfrom[1].user:lower() + -- Check for a match + if ef_user:find('+caf_=' .. verp, 1, true) then + local _,_,user = ef_user:find('^(.+)+caf_=') + if user then + user = user .. '@' .. envfrom[1].domain + return true, user + end + end + return false + end, + score = 0.0, + description = "Message was forwarded by Google", + group = "forwarding" } rspamd_config.FWD_YANDEX = { - callback = function (task) - if not (task:has_from(1) and task:has_recipients(1)) then - return false - end - local hostname = task:get_hostname() - if hostname and hostname:lower():find('%.yandex%.[a-z]+$') then - if task:get_header_raw('X-Yandex-Forward') then - return true - end - end - return false - end, - score = 0.0, - description = "Message was forwarded by Yandex", - group = "forwarding" + callback = function (task) + if not (task:has_from(1) and task:has_recipients(1)) then + return false + end + local hostname = task:get_hostname() + if hostname and hostname:lower():find('%.yandex%.[a-z]+$') then + if task:get_header_raw('X-Yandex-Forward') then + return true + end + end + return false + end, + score = 0.0, + description = "Message was forwarded by Yandex", + group = "forwarding" } rspamd_config.FWD_MAILRU = { - callback = function (task) - if not (task:has_from(1) and task:has_recipients(1)) then - return false - end - local hostname = task:get_hostname() - if hostname and hostname:lower():find('%.mail%.ru$') then - if task:get_header_raw('X-MailRu-Forward') then - return true - end - end - return false - end, - score = 0.0, - description = "Message was forwarded by Mail.ru", - group = "forwarding" + callback = function (task) + if not (task:has_from(1) and task:has_recipients(1)) then + return false + end + local hostname = task:get_hostname() + if hostname and hostname:lower():find('%.mail%.ru$') then + if task:get_header_raw('X-MailRu-Forward') then + return true + end + end + return false + end, + score = 0.0, + description = "Message was forwarded by Mail.ru", + group = "forwarding" } rspamd_config.FWD_SRS = { - callback = function (task) - if not (task:has_from(1) and task:has_recipients(1)) then - return false - end - local envfrom = task:get_from(1) - local envrcpts = task:get_recipients(1) - -- Forwarding is only to a single recipient - if #envrcpts > 1 then return false end - -- Get recipient and compute rewritten SRS address - local srs = '=' .. envrcpts[1].domain:lower() .. - '=' .. envrcpts[1].user:lower() - if envfrom[1].user:lower():find('^srs[01]=') and - envfrom[1].user:lower():find(srs, 1, false) - then - return true - end - return false - end, - score = 0.0, - description = "Message was forwarded using SRS", - group = "forwarding" + callback = function (task) + if not (task:has_from(1) and task:has_recipients(1)) then + return false + end + local envfrom = task:get_from(1) + local envrcpts = task:get_recipients(1) + -- Forwarding is only to a single recipient + if #envrcpts > 1 then return false end + -- Get recipient and compute rewritten SRS address + local srs = '=' .. envrcpts[1].domain:lower() .. + '=' .. envrcpts[1].user:lower() + if envfrom[1].user:lower():find('^srs[01]=') and + envfrom[1].user:lower():find(srs, 1, false) + then + return true + end + return false + end, + score = 0.0, + description = "Message was forwarded using SRS", + group = "forwarding" } rspamd_config.FORWARDED = { - callback = function (task) - if not task:has_recipients(1) then return false end - local envrcpts = task:get_recipients(1) - -- Forwarding will only be for single recipient messages - if #envrcpts > 1 then return false end - -- Get any other headers we might need - local lu = task:get_header('List-Unsubscribe') - local to = task:get_recipients(2) - local matches = 0 - -- Retrieve and loop through all Received headers - local rcvds = task:get_received_headers() + callback = function (task) + local function normalize_addr(addr) + addr = string.match(addr, '^?$') or addr + local cap, _,domain = string.match(addr, '^([^%+][^%+]*)(%+[^@]*)@(.*)$') + if cap then + addr = string.format('%s@%s', cap, domain) + end + + return addr + end + + if not task:has_recipients(1) then return false end + local envrcpts = task:get_recipients(1) + -- Forwarding will only be for single recipient messages + if #envrcpts > 1 then return false end + -- Get any other headers we might need + local lu = task:get_header('List-Unsubscribe') + local to = task:get_recipients(2) + local matches = 0 + -- Retrieve and loop through all Received headers + local rcvds = task:get_received_headers() - if rcvds then - for _, rcvd in ipairs(rcvds) do - local addr = rcvd['for'] - if addr then - matches = matches + 1 - -- Check that it doesn't match the envrcpt - -- TODO: remove any plus addressing? - if addr ~= envrcpts[1].addr:lower() then - -- Check for mailing-lists as they will have the same signature - if matches < 2 and lu and to and to[1].addr:lower() == addr then - return false - else - return true, addr - end - end - -- Prevent any other iterations as we only want - -- process the first matching Received header + if rcvds then + for _, rcvd in ipairs(rcvds) do + local addr = rcvd['for'] + if addr then + addr = normalize_addr(addr) + matches = matches + 1 + -- Check that it doesn't match the envrcpt + if addr ~= envrcpts[1].addr:lower() then + -- Check for mailing-lists as they will have the same signature + if matches < 2 and lu and to and to[1].addr:lower() == addr then return false + else + return true, 1.0, addr end end + -- Prevent any other iterations as we only want + -- process the first matching Received header + return false end - return false - end, - score = 0.0, - description = "Message was forwarded", - group = "forwarding" + end + end + return false + end, + score = 0.0, + description = "Message was forwarded", + group = "forwarding" } -- 2.39.5