aboutsummaryrefslogtreecommitdiffstats
path: root/rules/forwarding.lua
blob: 002c9df7add71cf3c698dd40528e969dd7726a82 (plain)
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
--[[
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 #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"
}

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"
}

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"
}

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_header_full('Received')

        if rcvds then
          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
        end
        return false
    end,
    score = 0.0,
    description = "Message was forwarded",
    group = "forwarding"
}