diff options
Diffstat (limited to 'rules/misc.lua')
-rw-r--r-- | rules/misc.lua | 199 |
1 files changed, 121 insertions, 78 deletions
diff --git a/rules/misc.lua b/rules/misc.lua index b27a1bc53..17e3b8ac7 100644 --- a/rules/misc.lua +++ b/rules/misc.lua @@ -47,7 +47,7 @@ rspamd_config.R_PARTS_DIFFER = { score = (nd - 0.5) end task:insert_result('R_PARTS_DIFFER', score, - string.format('%.1f%%', tostring(100.0 * nd))) + string.format('%.1f%%', tostring(100.0 * nd))) end end end @@ -75,15 +75,15 @@ local date_id = rspamd_config:register_symbol({ return end - local dt = task:get_date({format = 'connect', gmt = true}) + local dt = task:get_date({ format = 'connect', gmt = true }) local date_diff = dt - dm if date_diff > 86400 then -- Older than a day - task:insert_result('DATE_IN_PAST', 1.0, tostring(math.floor(date_diff/3600))) + task:insert_result('DATE_IN_PAST', 1.0, tostring(math.floor(date_diff / 3600))) elseif -date_diff > 7200 then -- More than 2 hours in the future - task:insert_result('DATE_IN_FUTURE', 1.0, tostring(math.floor(-date_diff/3600))) + task:insert_result('DATE_IN_FUTURE', 1.0, tostring(math.floor(-date_diff / 3600))) end end }) @@ -124,15 +124,15 @@ rspamd_config:register_symbol({ parent = date_id, }) -local obscured_id = rspamd_config:register_symbol{ +local obscured_id = rspamd_config:register_symbol { callback = function(task) - local susp_urls = task:get_urls_filtered({ 'obscured', 'zw_spaces'}) + local susp_urls = task:get_urls_filtered({ 'obscured', 'zw_spaces' }) if susp_urls and susp_urls[1] then local obs_flag = url_flags_tab.obscured local zw_flag = url_flags_tab.zw_spaces - for _,u in ipairs(susp_urls) do + for _, u in ipairs(susp_urls) do local fl = u:get_flags_num() if bit.band(fl, obs_flag) ~= 0 then task:insert_result('R_SUSPICIOUS_URL', 1.0, u:get_host()) @@ -152,7 +152,7 @@ local obscured_id = rspamd_config:register_symbol{ group = 'url' } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', name = 'ZERO_WIDTH_SPACE_URL', score = 7.0, @@ -162,9 +162,8 @@ rspamd_config:register_symbol{ parent = obscured_id, } - rspamd_config.ENVFROM_PRVS = { - callback = function (task) + callback = function(task) --[[ Detect PRVS/BATV addresses to avoid FORGED_SENDER https://en.wikipedia.org/wiki/Bounce_Address_Tag_Validation @@ -183,7 +182,9 @@ rspamd_config.ENVFROM_PRVS = { local re_text = '^(?:(prvs|msprvs1)=([^=]+)=|btv1==[^=]+==)(.+@(.+))$' local re = rspamd_regexp.create_cached(re_text) local c = re:search(envfrom[1].addr:lower(), false, true) - if not c then return false end + if not c then + return false + end local ef = c[1][4] -- See if it matches the From header local from = task:get_from(2) @@ -207,23 +208,25 @@ rspamd_config.ENVFROM_PRVS = { } rspamd_config.ENVFROM_VERP = { - callback = function (task) + 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) -- VERP only works for single recipient messages - if #envrcpts > 1 then return false end + if #envrcpts > 1 then + return false + end -- Get recipient and compute VERP address local rcpt = envrcpts[1].addr:lower() - local verp = rcpt:gsub('@','=') + local verp = rcpt:gsub('@', '=') -- Get the user portion of the envfrom local ef_user = envfrom[1].user:lower() -- See if the VERP representation of the recipient appears in it if ef_user:find(verp, 1, true) - and not ef_user:find('+caf_=' .. verp, 1, true) -- Google Forwarding - and not ef_user:find('^srs[01]=') -- SRS + and not ef_user:find('+caf_=' .. verp, 1, true) -- Google Forwarding + and not ef_user:find('^srs[01]=') -- SRS then return true end @@ -235,12 +238,14 @@ rspamd_config.ENVFROM_VERP = { type = 'mime', } -local check_rcvd = rspamd_config:register_symbol{ +local check_rcvd = rspamd_config:register_symbol { name = 'CHECK_RCVD', group = 'headers', - callback = function (task) + callback = function(task) local rcvds = task:get_received_headers() - if not rcvds or #rcvds == 0 then return false end + if not rcvds or #rcvds == 0 then + return false + end local all_tls = fun.all(function(rc) return rc.flags and rc.flags['ssl'] @@ -275,7 +280,7 @@ local check_rcvd = rspamd_config:register_symbol{ type = 'callback,mime', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_rcvd, name = 'RCVD_TLS_ALL', @@ -284,7 +289,7 @@ rspamd_config:register_symbol{ group = 'headers' } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_rcvd, name = 'RCVD_TLS_LAST', @@ -293,7 +298,7 @@ rspamd_config:register_symbol{ group = 'headers' } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_rcvd, name = 'RCVD_NO_TLS_LAST', @@ -302,7 +307,7 @@ rspamd_config:register_symbol{ group = 'headers' } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_rcvd, name = 'RCVD_VIA_SMTP_AUTH', @@ -313,7 +318,7 @@ rspamd_config:register_symbol{ } rspamd_config.RCVD_HELO_USER = { - callback = function (task) + callback = function(task) -- Check HELO argument from MTA local helo = task:get_helo() if (helo and helo:lower():find('^user$')) then @@ -321,11 +326,17 @@ rspamd_config.RCVD_HELO_USER = { end -- Check Received headers local rcvds = task:get_header_full('Received') - if not rcvds then return false end + if not rcvds then + return false + end for _, rcvd in ipairs(rcvds) do local r = rcvd['decoded']:lower() - if (r:find("^%s*from%suser%s")) then return true end - if (r:find("helo[%s=]user[%s%)]")) then return true end + if (r:find("^%s*from%suser%s")) then + return true + end + if (r:find("helo[%s=]user[%s%)]")) then + return true + end end end, description = 'HELO User spam pattern', @@ -335,11 +346,13 @@ rspamd_config.RCVD_HELO_USER = { } rspamd_config.URI_COUNT_ODD = { - callback = function (task) + callback = function(task) local ct = task:get_header('Content-Type') if (ct and ct:lower():find('^multipart/alternative')) then - local urls = task:get_urls_filtered(nil, {'subject', 'html_displayed', 'special'}) or {} - local nurls = fun.foldl(function(acc, val) return acc + val:get_count() end, 0, urls) + local urls = task:get_urls_filtered(nil, { 'subject', 'html_displayed', 'special' }) or {} + local nurls = fun.foldl(function(acc, val) + return acc + val:get_count() + end, 0, urls) if nurls % 2 == 1 then return true, 1.0, tostring(nurls) @@ -352,7 +365,7 @@ rspamd_config.URI_COUNT_ODD = { } rspamd_config.HAS_ATTACHMENT = { - callback = function (task) + callback = function(task) local parts = task:get_parts() if parts and #parts > 1 then for _, p in ipairs(parts) do @@ -376,7 +389,7 @@ local function freemail_reply_neq_from(task) local ff = task:get_symbol('FREEMAIL_FROM') local frt_opts = frt[1]['options'] local ff_opts = ff[1]['options'] - return ( frt_opts and ff_opts and frt_opts[1] ~= ff_opts[1] ) + return (frt_opts and ff_opts and frt_opts[1] ~= ff_opts[1]) end rspamd_config:register_symbol({ @@ -404,7 +417,8 @@ rspamd_config.OMOGRAPH_URL = { local h1 = u:get_host() local h2 = u:get_phished() - if h2 then -- Due to changes of the phished flag in 2.8 + if h2 then + -- Due to changes of the phished flag in 2.8 h2 = h2:get_host() end if h1 and h2 then @@ -448,20 +462,20 @@ rspamd_config.URL_IN_SUBJECT = { local urls = task:get_urls() if urls then - for _,u in ipairs(urls) do + for _, u in ipairs(urls) do local flags = u:get_flags() if flags.subject then if flags.schemaless then - return true,0.1,u:get_host() + return true, 0.1, u:get_host() end local subject = task:get_subject() if subject then if tostring(u) == subject then - return true,1.0,u:get_host() + return true, 1.0, u:get_host() end end - return true,0.25,u:get_host() + return true, 0.25, u:get_host() end end end @@ -474,18 +488,20 @@ rspamd_config.URL_IN_SUBJECT = { description = 'Subject contains URL' } -local aliases_id = rspamd_config:register_symbol{ +local aliases_id = rspamd_config:register_symbol { type = 'prefilter', name = 'EMAIL_PLUS_ALIASES', callback = function(task) local function check_from(type) if task:has_from(type) then local addr = task:get_from(type)[1] - local na,tags = lua_util.remove_email_aliases(addr) + local na, tags = lua_util.remove_email_aliases(addr) if na then task:set_from(type, addr, 'alias') task:insert_result('TAGGED_FROM', 1.0, fun.totable( - fun.filter(function(t) return t and #t > 0 end, tags))) + fun.filter(function(t) + return t and #t > 0 + end, tags))) end end end @@ -500,11 +516,15 @@ local aliases_id = rspamd_config:register_symbol{ local addrs = task:get_recipients(type) for _, addr in ipairs(addrs) do - local na,tags = lua_util.remove_email_aliases(addr) + local na, tags = lua_util.remove_email_aliases(addr) if na then modified = true - fun.each(function(t) table.insert(all_tags, t) end, - fun.filter(function(t) return t and #t > 0 end, tags)) + fun.each(function(t) + table.insert(all_tags, t) + end, + fun.filter(function(t) + return t and #t > 0 + end, tags)) end end @@ -523,7 +543,7 @@ local aliases_id = rspamd_config:register_symbol{ group = 'headers', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = aliases_id, name = 'TAGGED_RCPT', @@ -531,7 +551,7 @@ rspamd_config:register_symbol{ group = 'headers', score = 0.0, } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = aliases_id, name = 'TAGGED_FROM', @@ -540,18 +560,26 @@ rspamd_config:register_symbol{ score = 0.0, } -local check_from_display_name = rspamd_config:register_symbol{ +local check_from_display_name = rspamd_config:register_symbol { type = 'callback,mime', name = 'FROM_DISPLAY_CALLBACK', - callback = function (task) + callback = function(task) local from = task:get_from(2) - if not (from and from[1] and from[1].name) then return false end + if not (from and from[1] and from[1].name) then + return false + end -- See if we can parse an email address from the name local parsed = rspamd_parsers.parse_mail_address(from[1].name, task:get_mempool()) - if not parsed then return false end - if not (parsed[1] and parsed[1]['addr']) then return false end + if not parsed then + return false + end + if not (parsed[1] and parsed[1]['addr']) then + return false + end -- Make sure we did not mistake e.g. <something>@<name> for an email address - if not parsed[1]['domain'] or not parsed[1]['domain']:find('%.') then return false end + if not parsed[1]['domain'] or not parsed[1]['domain']:find('%.') then + return false + end -- See if the parsed domains differ if not rspamd_util.strequal_caseless(from[1]['domain'], parsed[1]['domain']) then -- See if the destination domain is the same as the spoof @@ -580,7 +608,7 @@ local check_from_display_name = rspamd_config:register_symbol{ group = 'headers', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_from_display_name, name = 'SPOOF_DISPLAY_NAME', @@ -589,7 +617,7 @@ rspamd_config:register_symbol{ score = 8.0, } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_from_display_name, name = 'FROM_NEQ_DISPLAY_NAME', @@ -599,15 +627,19 @@ rspamd_config:register_symbol{ } rspamd_config.SPOOF_REPLYTO = { - callback = function (task) + callback = function(task) -- First check for a Reply-To header local rt = task:get_header_full('Reply-To') - if not rt or not rt[1] then return false end + if not rt or not rt[1] then + return false + end -- Get From and To headers rt = rt[1]['value'] local from = task:get_from(2) local to = task:get_recipients(2) - if not (from and from[1] and from[1].addr) then return false end + if not (from and from[1] and from[1].addr) then + return false + end if (to and to[1] and to[1].addr) then -- Handle common case for Web Contact forms of From = To if rspamd_util.strequal_caseless(from[1].addr, to[1].addr) then @@ -616,9 +648,13 @@ rspamd_config.SPOOF_REPLYTO = { end -- SMTP recipients must contain From domain to = task:get_recipients(1) - if not to then return false end + if not to then + return false + end -- Try mitigate some possible FPs on mailing list posts - if #to == 1 and rspamd_util.strequal_caseless(to[1].addr, from[1].addr) then return false end + if #to == 1 and rspamd_util.strequal_caseless(to[1].addr, from[1].addr) then + return false + end local found_fromdom = false for _, t in ipairs(to) do if rspamd_util.strequal_caseless(t.domain, from[1].domain) then @@ -626,10 +662,14 @@ rspamd_config.SPOOF_REPLYTO = { break end end - if not found_fromdom then return false end + if not found_fromdom then + return false + end -- Parse Reply-To header local parsed = ((rspamd_parsers.parse_mail_address(rt, task:get_mempool()) or E)[1] or E).domain - if not parsed then return false end + if not parsed then + return false + end -- Reply-To domain must be different to From domain if not rspamd_util.strequal_caseless(parsed, from[1].domain) then return true, from[1].domain, parsed @@ -652,14 +692,18 @@ rspamd_config.INFO_TO_INFO_LU = { return false end local to = task:get_recipients('smtp') - if not to then return false end + if not to then + return false + end local found = false - for _,r in ipairs(to) do + for _, r in ipairs(to) do if rspamd_util.strequal_caseless(r['user'], 'info') then found = true end end - if found then return true end + if found then + return true + end return false end, description = 'info@ From/To address with List-Unsubscribe headers', @@ -674,12 +718,12 @@ rspamd_config.R_BAD_CTE_7BIT = { callback = function(task) local tp = task:get_text_parts() or {} - for _,p in ipairs(tp) do + for _, p in ipairs(tp) do local cte = p:get_mimepart():get_cte() or '' if cte ~= '8bit' and p:has_8bit_raw() then - local _,_,attrs = p:get_mimepart():get_type_full() + local _, _, attrs = p:get_mimepart():get_type_full() local mul = 1.0 - local params = {cte} + local params = { cte } if attrs then if attrs.charset and attrs.charset:lower() == "utf-8" then -- Penalise rule as people don't know that utf8 is surprisingly @@ -689,7 +733,7 @@ rspamd_config.R_BAD_CTE_7BIT = { end end - return true,mul,params + return true, mul, params end end @@ -701,8 +745,7 @@ rspamd_config.R_BAD_CTE_7BIT = { type = 'mime', } - -local check_encrypted_name = rspamd_config:register_symbol{ +local check_encrypted_name = rspamd_config:register_symbol { name = 'BOGUS_ENCRYPTED_AND_TEXT', callback = function(task) local parts = task:get_parts() or {} @@ -714,14 +757,14 @@ local check_encrypted_name = rspamd_config:register_symbol{ local children = part:get_children() or {} local text_kids = {} - for _,cld in ipairs(children) do + for _, cld in ipairs(children) do if cld:is_multipart() then check_part(cld) elseif cld:is_text() then seen_text = true text_kids[#text_kids + 1] = cld else - local type,subtype,_ = cld:get_type_full() + local type, subtype, _ = cld:get_type_full() if type:lower() == 'application' then if string.find(subtype:lower(), 'pkcs7%-mime') then @@ -743,8 +786,8 @@ local check_encrypted_name = rspamd_config:register_symbol{ end if seen_text and seen_encrypted then -- Ensure that our seen text is not really part of pgp #3205 - for _,tp in ipairs(text_kids) do - local t,_ = tp:get_type() + for _, tp in ipairs(text_kids) do + local t, _ = tp:get_type() seen_text = false -- reset temporary if t and t == 'text' then seen_text = true @@ -756,7 +799,7 @@ local check_encrypted_name = rspamd_config:register_symbol{ end end - for _,part in ipairs(parts) do + for _, part in ipairs(parts) do check_part(part) end @@ -771,7 +814,7 @@ local check_encrypted_name = rspamd_config:register_symbol{ group = 'mime_types', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_encrypted_name, name = 'ENCRYPTED_PGP', @@ -781,7 +824,7 @@ rspamd_config:register_symbol{ one_shot = true } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_encrypted_name, name = 'ENCRYPTED_SMIME', @@ -791,7 +834,7 @@ rspamd_config:register_symbol{ one_shot = true } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_encrypted_name, name = 'SIGNED_PGP', @@ -801,7 +844,7 @@ rspamd_config:register_symbol{ one_shot = true } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { type = 'virtual', parent = check_encrypted_name, name = 'SIGNED_SMIME', |