aboutsummaryrefslogtreecommitdiffstats
path: root/rules
diff options
context:
space:
mode:
authorSteve Freegard <steve@stevefreegard.com>2016-04-22 12:20:21 +0100
committerSteve Freegard <steve@stevefreegard.com>2016-04-22 12:20:21 +0100
commite36bf64097a27ee6d0595b6923f9a811d49ddb09 (patch)
tree63c061ed77b0fd084073067af300fc841be2abe9 /rules
parent0cdf354cb4424eae9bc1ccdec865e7435090ec83 (diff)
downloadrspamd-e36bf64097a27ee6d0595b6923f9a811d49ddb09.tar.gz
rspamd-e36bf64097a27ee6d0595b6923f9a811d49ddb09.zip
New rules
Diffstat (limited to 'rules')
-rw-r--r--rules/forwarding.lua109
-rw-r--r--rules/misc.lua74
-rw-r--r--rules/regexp/upstream_spam_filters.lua46
-rw-r--r--rules/rspamd.lua2
4 files changed, 231 insertions, 0 deletions
diff --git a/rules/forwarding.lua b/rules/forwarding.lua
new file mode 100644
index 000000000..6ee0b9a97
--- /dev/null
+++ b/rules/forwarding.lua
@@ -0,0 +1,109 @@
+--[[
+Copyright (c) 2011-2016, Vsevolod Stakhov <vsevolod@highsecure.ru>
+
+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.
+]]--
+
+-- Rules to detect forwarding
+
+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 table.getn(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.1,
+ description = "Message was forwarded by Google",
+ 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 table.getn(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.1,
+ 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 table.getn(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_header_full('Received')
+ for _, rcvd in ipairs(rcvds) do
+ local _,_,addr = rcvd['decoded']:lower():find("%sfor%s<(.-)>")
+ 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
+ return false
+ end
+ end
+ return false
+ end,
+ score = 0.1,
+ description = "Message was forwarded",
+ group = "forwarding"
+}
+
diff --git a/rules/misc.lua b/rules/misc.lua
index b3926e46b..f72efea87 100644
--- a/rules/misc.lua
+++ b/rules/misc.lua
@@ -237,3 +237,77 @@ rspamd_config.MULTIPLE_UNIQUE_HEADERS = {
group = 'headers',
description = 'Repeated unique headers'
}
+
+rspamd_config.ENVFROM_PRVS = {
+ callback = function (task)
+ -- Detect PRVS/BATV addresses to avoid FORGED_SENDER
+ -- https://en.wikipedia.org/wiki/Bounce_Address_Tag_Validation
+ if not (task:has_from(1) and task:has_from(2)) then
+ return false
+ end
+ local envfrom = task:get_from(1)
+ local tag,ef = envfrom[1].addr:lower():match("^prvs=([^=]+)=(.+)$")
+ if not ef then return false end
+ -- See if it matches the From header
+ local from = task:get_from(2)
+ if ef == from[1].addr:lower() then
+ return true
+ end
+ return false
+ end,
+ score = 0.01,
+ description = "Envelope From is a PRVS address that matches the From address",
+ group = 'prvs'
+}
+
+rspamd_config.ENVFROM_VERP = {
+ 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 table.getn(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()
+ -- 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
+ then
+ return true
+ end
+ return false
+ end,
+ score = 0.01,
+ description = "Envelope From is a VERP address",
+ group = "mailing_list"
+}
+
+rspamd_config.RCVD_TLS_ALL = {
+ callback = function (task)
+ local rcvds = task:get_header_full('Received')
+ local count = 0
+ local encrypted = 0
+ for _, rcvd in ipairs(rcvds) do
+ count = count + 1
+ local r = rcvd['decoded']:lower()
+ local by = r:match('^by%s+([^%s]+)') or r:match('%sby%s+([^%s]+)')
+ local with = r:match('%swith%s+(e?smtps?a?)')
+ if with and with:match('esmtps') then
+ encrypted = encrypted + 1
+ end
+ end
+ if (count > 0 and count == encrypted) then
+ return true
+ end
+ end,
+ score = 0.01,
+ description = "All hops used encrypted transports",
+ group = "encryption"
+}
+
diff --git a/rules/regexp/upstream_spam_filters.lua b/rules/regexp/upstream_spam_filters.lua
new file mode 100644
index 000000000..066b8fd57
--- /dev/null
+++ b/rules/regexp/upstream_spam_filters.lua
@@ -0,0 +1,46 @@
+--[[
+Copyright (c) 2011-2016, Vsevolod Stakhov <vsevolod@highsecure.ru>
+
+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.
+]]--
+
+-- Rules for upstream services that have already run spam checks
+
+reconf['PRECEDENCE_BULK'] = {
+ re = 'Precedence=/bulk/Hi',
+ score = 3,
+ description = "Message marked as bulk",
+ group = 'upstream_spam_filters'
+}
+
+reconf['MICROSOFT_SPAM'] = {
+ -- https://technet.microsoft.com/en-us/library/dn205071(v=exchg.150).aspx
+ re = 'X-Forefront-Antispam-Report=/SFV:SPM/H',
+ score = 10,
+ description = "Microsoft says the messge is spam",
+ group = 'upstream_spam_filters'
+}
+
+reconf['AOL_SPAM'] = {
+ re = 'X-AOL-Global-Disposition=/^S/H',
+ score = 5,
+ description = "AOL says this message is spam",
+ group = 'upstream_spam_filters'
+}
+
+reconf['SPAM_FLAG'] = {
+ re = 'X-Spam-Flag=/^(?:yes|true)/Hi',
+ score = 5,
+ description = "Message was already marked as spam",
+ group = 'upstream_spam_filters'
+}
diff --git a/rules/rspamd.lua b/rules/rspamd.lua
index 4c6ce61d9..a3cdc919b 100644
--- a/rules/rspamd.lua
+++ b/rules/rspamd.lua
@@ -28,9 +28,11 @@ dofile(local_rules .. '/regexp/headers.lua')
dofile(local_rules .. '/regexp/lotto.lua')
dofile(local_rules .. '/regexp/fraud.lua')
dofile(local_rules .. '/regexp/drugs.lua')
+dofile(local_rules .. '/regexp/upstream_spam_filters.lua')
dofile(local_rules .. '/html.lua')
dofile(local_rules .. '/misc.lua')
dofile(local_rules .. '/http_headers.lua')
+dofile(local_rules .. '/forwarding.lua')
local function file_exists(filename)
local file = io.open(filename)