1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
--[[
Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
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
local rspamd_util = require "rspamd_util"
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 { 'smtp', 'orig' }
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
return task:has_header('X-Yandex-Forward')
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
return task:has_header('X-MailRu-Forward')
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 Sender Rewriting Scheme (SRS)",
group = "forwarding"
}
rspamd_config.FORWARDED = {
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) or not task:has_recipients(2) 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 has_list_unsub = task:has_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
addr = normalize_addr(addr)
matches = matches + 1
-- Check that it doesn't match the envrcpt
if not rspamd_util.strequal_caseless(addr, envrcpts[1].addr) then
-- Check for mailing-lists as they will have the same signature
if matches < 2 and has_list_unsub and to and rspamd_util.strequal_caseless(to[1].addr, 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
end
end
return false
end,
score = 0.0,
description = "Message was forwarded",
group = "forwarding"
}
|