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.

misc.lua 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. --[[
  2. Copyright (c) 2011-2015, 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. -- This is main lua config file for rspamd
  14. local util = require "rspamd_util"
  15. local rspamd_regexp = require "rspamd_regexp"
  16. local rspamd_logger = require "rspamd_logger"
  17. local reconf = config['regexp']
  18. -- Uncategorized rules
  19. local subject_re = rspamd_regexp.create('/^(?:(?:Re|Fwd|Fw|Aw|Antwort|Sv):\\s*)+(.+)$/i')
  20. -- Local rules
  21. local r_bgcolor = '/BGCOLOR=/iP'
  22. local r_font_color = '/font color=[\\"\']?\\#FFFFFF[\\"\']?/iP'
  23. reconf['R_WHITE_ON_WHITE'] = string.format('(!(%s) & (%s))', r_bgcolor, r_font_color)
  24. reconf['R_FLASH_REDIR_IMGSHACK'] = '/^(?:http:\\/\\/)?img\\d{1,5}\\.imageshack\\.us\\/\\S+\\.swf/U'
  25. -- Different text parts
  26. rspamd_config.R_PARTS_DIFFER = function(task)
  27. local distance = task:get_mempool():get_variable('parts_distance', 'int')
  28. if distance then
  29. local nd = tonumber(distance)
  30. if nd < 50 then
  31. local score = 1 - util.tanh(nd / 100.0)
  32. task:insert_result('R_PARTS_DIFFER', score, tostring(nd) .. '%')
  33. end
  34. end
  35. return false
  36. end
  37. -- Date issues
  38. rspamd_config.MISSING_DATE = function(task)
  39. if rspamd_config:get_api_version() >= 5 then
  40. if not task:get_header_raw('Date') then
  41. return true
  42. end
  43. end
  44. return false
  45. end
  46. rspamd_config.DATE_IN_FUTURE = function(task)
  47. if rspamd_config:get_api_version() >= 5 then
  48. local dm = task:get_date{format = 'message'}
  49. local dt = task:get_date{format = 'connect'}
  50. -- An 2 hour
  51. if dm > 0 and dm - dt > 7200 then
  52. return true
  53. end
  54. end
  55. return false
  56. end
  57. rspamd_config.DATE_IN_PAST = function(task)
  58. if rspamd_config:get_api_version() >= 5 then
  59. local dm = task:get_date{format = 'message', gmt = true}
  60. local dt = task:get_date{format = 'connect', gmt = true}
  61. -- A day
  62. if dm > 0 and dt - dm > 86400 then
  63. return true
  64. end
  65. end
  66. return false
  67. end
  68. rspamd_config.R_SUSPICIOUS_URL = {
  69. callback = function(task)
  70. local urls = task:get_urls()
  71. if urls then
  72. for i,u in ipairs(urls) do
  73. if u:is_obscured() then
  74. return true
  75. end
  76. end
  77. end
  78. return false
  79. end,
  80. score = 6.0,
  81. group = 'url',
  82. one_shot = true,
  83. description = 'Obfusicated or suspicious URL has been found in a message'
  84. }
  85. rspamd_config.SUBJ_ALL_CAPS = {
  86. callback = function(task)
  87. local sbj = task:get_header('Subject')
  88. if sbj then
  89. local stripped_subject = subject_re:search(sbj, false, true)
  90. if stripped_subject and stripped_subject[1] and stripped_subject[1][2] then
  91. sbj = stripped_subject[1][2]
  92. end
  93. if util.is_uppercase(sbj) then
  94. return true
  95. end
  96. end
  97. return false
  98. end,
  99. score = 3.0,
  100. group = 'headers',
  101. description = 'All capital letters in subject'
  102. }
  103. rspamd_config.LONG_SUBJ = {
  104. callback = function(task)
  105. local sbj = task:get_header('Subject')
  106. if sbj and util.strlen_utf8(sbj) > 200 then
  107. return true
  108. end
  109. return false
  110. end,
  111. score = 3.0,
  112. group = 'headers',
  113. description = 'Subject is too long'
  114. }
  115. rspamd_config.BROKEN_HEADERS = {
  116. callback = function(task)
  117. if task:has_flag('broken_headers') then
  118. return true
  119. end
  120. return false
  121. end,
  122. score = 1.0,
  123. group = 'headers',
  124. description = 'Headers structure is likely broken'
  125. }
  126. rspamd_config.HEADER_RCONFIRM_MISMATCH = {
  127. callback = function (task)
  128. local header_from = nil
  129. local cread = task:get_header('X-Confirm-Reading-To')
  130. if task:has_from('mime') then
  131. header_from = task:get_from('mime')[1]
  132. end
  133. local header_cread = nil
  134. if cread then
  135. local headers_cread = util.parse_mail_address(cread)
  136. if headers_cread then header_cread = headers_cread[1] end
  137. end
  138. if header_from and header_cread then
  139. if not string.find(header_from['addr'], header_cread['addr']) then
  140. return true
  141. end
  142. end
  143. return false
  144. end,
  145. score = 2.0,
  146. group = 'headers',
  147. description = 'Read confirmation address is different to from address'
  148. }
  149. rspamd_config.HEADER_FORGED_MDN = {
  150. callback = function (task)
  151. local mdn = task:get_header('Disposition-Notification-To')
  152. local header_rp = nil
  153. if task:has_from('smtp') then
  154. header_rp = task:get_from('smtp')[1]
  155. end
  156. -- Parse mail addr
  157. local header_mdn = nil
  158. if mdn then
  159. local headers_mdn = util.parse_mail_address(mdn)
  160. if headers_mdn then header_mdn = headers_mdn[1] end
  161. end
  162. if header_mdn and not header_rp then return true end
  163. if header_rp and not header_mdn then return false end
  164. if header_mdn and header_mdn['addr'] ~= header_rp['addr'] then
  165. return true
  166. end
  167. return false
  168. end,
  169. score = 2.0,
  170. group = 'headers',
  171. description = 'Read confirmation address is different to return path'
  172. }
  173. local headers_unique = {
  174. 'Content-Type',
  175. 'Content-Transfer-Encoding',
  176. 'Date',
  177. 'Message-ID'
  178. }
  179. rspamd_config.MULTIPLE_UNIQUE_HEADERS = {
  180. callback = function (task)
  181. local res = 0
  182. local res_tbl = {}
  183. for i,hdr in ipairs(headers_unique) do
  184. local h = task:get_header_full(hdr)
  185. if h and #h > 1 then
  186. res = res + 1
  187. table.insert(res_tbl, hdr)
  188. end
  189. end
  190. if res > 0 then
  191. return true,res,table.concat(res_tbl, ',')
  192. end
  193. return false
  194. end,
  195. score = 5.0,
  196. group = 'headers',
  197. description = 'Repeated unique headers'
  198. }