You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

forwarding.lua 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. --[[
  2. Copyright (c) 2011-2016, Vsevolod Stakhov <vsevolod@highsecure.ru>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. -- Rules to detect forwarding
  14. local rspamd_util = require "rspamd_util"
  15. rspamd_config.FWD_GOOGLE = {
  16. callback = function (task)
  17. if not (task:has_from(1) and task:has_recipients(1)) then
  18. return false
  19. end
  20. local envfrom = task:get_from(1)
  21. local envrcpts = task:get_recipients(1)
  22. -- Forwarding will only be to a single recipient
  23. if #envrcpts > 1 then return false end
  24. -- Get recipient and compute VERP address
  25. local rcpt = envrcpts[1].addr:lower()
  26. local verp = rcpt:gsub('@','=')
  27. -- Get the user portion of the envfrom
  28. local ef_user = envfrom[1].user:lower()
  29. -- Check for a match
  30. if ef_user:find('+caf_=' .. verp, 1, true) then
  31. local _,_,user = ef_user:find('^(.+)+caf_=')
  32. if user then
  33. user = user .. '@' .. envfrom[1].domain
  34. return true, user
  35. end
  36. end
  37. return false
  38. end,
  39. score = 0.0,
  40. description = "Message was forwarded by Google",
  41. group = "forwarding"
  42. }
  43. rspamd_config.FWD_YANDEX = {
  44. callback = function (task)
  45. if not (task:has_from(1) and task:has_recipients(1)) then
  46. return false
  47. end
  48. local hostname = task:get_hostname()
  49. if hostname and hostname:lower():find('%.yandex%.[a-z]+$') then
  50. if task:get_header_raw('X-Yandex-Forward') then
  51. return true
  52. end
  53. end
  54. return false
  55. end,
  56. score = 0.0,
  57. description = "Message was forwarded by Yandex",
  58. group = "forwarding"
  59. }
  60. rspamd_config.FWD_MAILRU = {
  61. callback = function (task)
  62. if not (task:has_from(1) and task:has_recipients(1)) then
  63. return false
  64. end
  65. local hostname = task:get_hostname()
  66. if hostname and hostname:lower():find('%.mail%.ru$') then
  67. if task:get_header_raw('X-MailRu-Forward') then
  68. return true
  69. end
  70. end
  71. return false
  72. end,
  73. score = 0.0,
  74. description = "Message was forwarded by Mail.ru",
  75. group = "forwarding"
  76. }
  77. rspamd_config.FWD_SRS = {
  78. callback = function (task)
  79. if not (task:has_from(1) and task:has_recipients(1)) then
  80. return false
  81. end
  82. local envfrom = task:get_from(1)
  83. local envrcpts = task:get_recipients(1)
  84. -- Forwarding is only to a single recipient
  85. if #envrcpts > 1 then return false end
  86. -- Get recipient and compute rewritten SRS address
  87. local srs = '=' .. envrcpts[1].domain:lower() ..
  88. '=' .. envrcpts[1].user:lower()
  89. if envfrom[1].user:lower():find('^srs[01]=') and
  90. envfrom[1].user:lower():find(srs, 1, false)
  91. then
  92. return true
  93. end
  94. return false
  95. end,
  96. score = 0.0,
  97. description = "Message was forwarded using Sender Rewriting Scheme (SRS)",
  98. group = "forwarding"
  99. }
  100. rspamd_config.FORWARDED = {
  101. callback = function (task)
  102. local function normalize_addr(addr)
  103. addr = string.match(addr, '^<?([^>]*)>?$') or addr
  104. local cap, _,domain = string.match(addr, '^([^%+][^%+]*)(%+[^@]*)@(.*)$')
  105. if cap then
  106. addr = string.format('%s@%s', cap, domain)
  107. end
  108. return addr
  109. end
  110. if not task:has_recipients(1) then return false end
  111. local envrcpts = task:get_recipients(1)
  112. -- Forwarding will only be for single recipient messages
  113. if #envrcpts > 1 then return false end
  114. -- Get any other headers we might need
  115. local lu = task:get_header('List-Unsubscribe')
  116. local to = task:get_recipients(2)
  117. local matches = 0
  118. -- Retrieve and loop through all Received headers
  119. local rcvds = task:get_received_headers()
  120. if rcvds then
  121. for _, rcvd in ipairs(rcvds) do
  122. local addr = rcvd['for']
  123. if addr then
  124. addr = normalize_addr(addr)
  125. matches = matches + 1
  126. -- Check that it doesn't match the envrcpt
  127. if not rspamd_util.strequal_caseless(addr, envrcpts[1].addr) then
  128. -- Check for mailing-lists as they will have the same signature
  129. if matches < 2 and lu and to and rspamd_util.strequal_caseless(to[1].addr, addr) then
  130. return false
  131. else
  132. return true, 1.0, addr
  133. end
  134. end
  135. -- Prevent any other iterations as we only want
  136. -- process the first matching Received header
  137. return false
  138. end
  139. end
  140. end
  141. return false
  142. end,
  143. score = 0.0,
  144. description = "Message was forwarded",
  145. group = "forwarding"
  146. }