diff options
Diffstat (limited to 'rules/mid.lua')
-rw-r--r-- | rules/mid.lua | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/rules/mid.lua b/rules/mid.lua new file mode 100644 index 0000000..1bac26c --- /dev/null +++ b/rules/mid.lua @@ -0,0 +1,131 @@ +--[[ +Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com> +Copyright (c) 2016, Steve Freegard <steve@freegard.name> + +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. +]]-- +local rspamd_util = require "rspamd_util" +local function mid_check_func(task) + local mid = task:get_header('Message-ID') + if not mid then + return false + end + -- Check for 'bare' IP addresses in RHS + if mid:find("@%d+%.%d+%.%d+%.%d+>$") then + task:insert_result('MID_BARE_IP', 1.0) + end + -- Check for non-FQDN RHS + if mid:find("@[^%.]+>?$") then + task:insert_result('MID_RHS_NOT_FQDN', 1.0) + end + -- Check for missing <>'s + if not mid:find('^<[^>]+>$') then + task:insert_result('MID_MISSING_BRACKETS', 1.0) + end + -- Check for IP literal in RHS + if mid:find("@%[%d+%.%d+%.%d+%.%d+%]") then + task:insert_result('MID_RHS_IP_LITERAL', 1.0) + end + -- Check From address attributes against MID + local from = task:get_from(2) + local fd + if (from and from[1] and from[1].domain and from[1].domain ~= '') then + fd = from[1].domain:lower() + local _, _, md = mid:find("@([^>]+)>?$") + -- See if all or part of the From address + -- can be found in the Message-ID + -- extract tld + local fdtld = nil + local mdtld = nil + if md then + fdtld = rspamd_util.get_tld(fd) + mdtld = rspamd_util.get_tld(md) + end + if (mid:lower():find(from[1].addr:lower(), 1, true)) then + task:insert_result('MID_CONTAINS_FROM', 1.0) + elseif (md and fd == md:lower()) then + task:insert_result('MID_RHS_MATCH_FROM', 1.0) + elseif (mdtld ~= nil and fdtld ~= nil and mdtld:lower() == fdtld) then + task:insert_result('MID_RHS_MATCH_FROMTLD', 1.0) + end + end + -- Check To address attributes against MID + local to = task:get_recipients(2) + if (to and to[1] and to[1].domain and to[1].domain ~= '') then + local td = to[1].domain:lower() + local _, _, md = mid:find("@([^>]+)>?$") + -- Skip if from domain == to domain + if ((fd and fd ~= td) or not fd) then + -- See if all or part of the To address + -- can be found in the Message-ID + if (mid:lower():find(to[1].addr:lower(), 1, true)) then + task:insert_result('MID_CONTAINS_TO', 1.0) + elseif (md and td == md:lower()) then + task:insert_result('MID_RHS_MATCH_TO', 1.0) + end + end + end +end + +-- MID checks from Steve Freegard +local check_mid_id = rspamd_config:register_symbol({ + name = 'CHECK_MID', + score = 0.0, + group = 'mid', + type = 'callback,mime', + callback = mid_check_func +}) +rspamd_config:register_virtual_symbol('MID_BARE_IP', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_BARE_IP', 2.0, 'Message-ID RHS is a bare IP address', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_RHS_NOT_FQDN', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_RHS_NOT_FQDN', 0.5, + 'Message-ID RHS is not a fully-qualified domain name', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_MISSING_BRACKETS', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_MISSING_BRACKETS', 0.5, 'Message-ID is missing <>\'s', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_RHS_IP_LITERAL', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_RHS_IP_LITERAL', 0.5, 'Message-ID RHS is an IP-literal', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_CONTAINS_FROM', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_CONTAINS_FROM', 1.0, 'Message-ID contains From address', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_RHS_MATCH_FROM', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_RHS_MATCH_FROM', 0.0, + 'Message-ID RHS matches From domain', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_RHS_MATCH_FROMTLD', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_RHS_MATCH_FROMTLD', 0.0, + 'Message-ID RHS matches From domain tld', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_CONTAINS_TO', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_CONTAINS_TO', 1.0, 'Message-ID contains To address', 'default', 'Message ID') +rspamd_config:register_virtual_symbol('MID_RHS_MATCH_TO', 1.0, check_mid_id) +rspamd_config:set_metric_symbol('MID_RHS_MATCH_TO', 1.0, 'Message-ID RHS matches To domain', 'default', 'Message ID') + +-- Another check from https://github.com/rspamd/rspamd/issues/4299 +rspamd_config:register_symbol { + type = 'normal,mime', + group = 'mid', + name = 'MID_END_EQ_FROM_USER_PART', + description = 'Message-ID RHS (after @) and MIME from local part are the same', + score = 4.0, + + callback = function(task) + local mid = task:get_header('Message-ID') + if not mid then + return + end + local mime_from = task:get_from('mime') + local _, _, mid_realm = mid:find("@([a-z]+)>?$") + if mid_realm and mime_from and mime_from[1] and mime_from[1].user then + if (mid_realm == mime_from[1].user) then + return true + end + end + end +} |