Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

spamassassin.lua 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  3. Copyright (c) 2019, Carsten Rosenberg <c.rosenberg@heinlein-support.de>
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. ]]--
  14. --[[[
  15. -- @module spamassassin
  16. -- This module contains spamd access functions.
  17. --]]
  18. local lua_util = require "lua_util"
  19. local tcp = require "rspamd_tcp"
  20. local upstream_list = require "rspamd_upstream_list"
  21. local rspamd_logger = require "rspamd_logger"
  22. local common = require "lua_scanners/common"
  23. local N = 'spamassassin'
  24. local function spamassassin_config(opts)
  25. local spamassassin_conf = {
  26. N = N,
  27. scan_mime_parts = false,
  28. scan_text_mime = false,
  29. scan_image_mime = false,
  30. default_port = 783,
  31. timeout = 15.0,
  32. log_clean = false,
  33. retransmits = 2,
  34. cache_expire = 3600, -- expire redis in one hour
  35. symbol = "SPAMD",
  36. message = '${SCANNER}: Spamassassin bulk message found: "${VIRUS}"',
  37. detection_category = "spam",
  38. default_score = 1,
  39. action = false,
  40. extended = false,
  41. symbol_type = 'postfilter',
  42. dynamic_scan = true,
  43. }
  44. spamassassin_conf = lua_util.override_defaults(spamassassin_conf, opts)
  45. if not spamassassin_conf.prefix then
  46. spamassassin_conf.prefix = 'rs_' .. spamassassin_conf.name .. '_'
  47. end
  48. if not spamassassin_conf.log_prefix then
  49. if spamassassin_conf.name:lower() == spamassassin_conf.type:lower() then
  50. spamassassin_conf.log_prefix = spamassassin_conf.name
  51. else
  52. spamassassin_conf.log_prefix = spamassassin_conf.name .. ' (' .. spamassassin_conf.type .. ')'
  53. end
  54. end
  55. if not spamassassin_conf.servers then
  56. rspamd_logger.errx(rspamd_config, 'no servers defined')
  57. return nil
  58. end
  59. spamassassin_conf.upstreams = upstream_list.create(rspamd_config,
  60. spamassassin_conf.servers,
  61. spamassassin_conf.default_port)
  62. if spamassassin_conf.upstreams then
  63. lua_util.add_debug_alias('external_services', spamassassin_conf.N)
  64. return spamassassin_conf
  65. end
  66. rspamd_logger.errx(rspamd_config, 'cannot parse servers %s',
  67. spamassassin_conf.servers)
  68. return nil
  69. end
  70. local function spamassassin_check(task, content, digest, rule)
  71. local function spamassassin_check_uncached ()
  72. local upstream = rule.upstreams:get_upstream_round_robin()
  73. local addr = upstream:get_addr()
  74. local retransmits = rule.retransmits
  75. -- Build the spamd query
  76. -- https://svn.apache.org/repos/asf/spamassassin/trunk/spamd/PROTOCOL
  77. local request_data = {
  78. "HEADERS SPAMC/1.5\r\n",
  79. "User: root\r\n",
  80. "Content-length: ".. #content .. "\r\n",
  81. "\r\n",
  82. content,
  83. }
  84. local function spamassassin_callback(err, data)
  85. local function spamassassin_requery(error)
  86. -- set current upstream to fail because an error occurred
  87. upstream:fail()
  88. -- retry with another upstream until retransmits exceeds
  89. if retransmits > 0 then
  90. retransmits = retransmits - 1
  91. lua_util.debugm(rule.N, task, '%s: Request Error: %s - retries left: %s',
  92. rule.log_prefix, error, retransmits)
  93. -- Select a different upstream!
  94. upstream = rule.upstreams:get_upstream_round_robin()
  95. addr = upstream:get_addr()
  96. lua_util.debugm(rule.N, task, '%s: retry IP: %s:%s',
  97. rule.log_prefix, addr, addr:get_port())
  98. tcp.request({
  99. task = task,
  100. host = addr:to_string(),
  101. port = addr:get_port(),
  102. timeout = rule['timeout'],
  103. data = request_data,
  104. callback = spamassassin_callback,
  105. })
  106. else
  107. rspamd_logger.errx(task, '%s: failed to scan, maximum retransmits '..
  108. 'exceed - err: %s', rule.log_prefix, error)
  109. common.yield_result(task, rule, 'failed to scan and retransmits exceed: ' .. error, 0.0, 'fail')
  110. end
  111. end
  112. if err then
  113. spamassassin_requery(err)
  114. else
  115. -- Parse the response
  116. if upstream then upstream:ok() end
  117. --lua_util.debugm(rule.N, task, '%s: returned result: %s', rule.log_prefix, data)
  118. --[[
  119. patterns tested against Spamassassin 3.4.6
  120. X-Spam-Status: No, score=1.1 required=5.0 tests=HTML_MESSAGE,MIME_HTML_ONLY,
  121. TVD_RCVD_SPACE_BRACKET,UNPARSEABLE_RELAY autolearn=no
  122. autolearn_force=no version=3.4.6
  123. ]] --
  124. local header = string.gsub(tostring(data), "[\r\n]+[\t ]", " ")
  125. --lua_util.debugm(rule.N, task, '%s: returned header: %s', rule.log_prefix, header)
  126. local symbols = ""
  127. local spam_score = 0
  128. for s in header:gmatch("[^\r\n]+") do
  129. if string.find(s, 'X%-Spam%-Status: %S+, score') then
  130. local pattern_symbols = "X%-Spam%-Status: %S+, score%=([%-%d%.]+)%s.*tests%=(.*,?)(%s*%S+)%sautolearn.*"
  131. spam_score = string.gsub(s, pattern_symbols, "%1")
  132. symbols = string.gsub(s, pattern_symbols, "%2%3")
  133. symbols = string.gsub(symbols, "%s", "")
  134. end
  135. end
  136. lua_util.debugm(rule.N, task, '%s: spam_score: %s, symbols: %s, int spam_score: |%s|, type spam_score: |%s|',
  137. rule.log_prefix, spam_score, symbols, tonumber(spam_score), type(spam_score))
  138. if tonumber(spam_score) > 0 and #symbols > 0 and symbols ~= "none" then
  139. if rule.extended == false then
  140. common.yield_result(task, rule, symbols, spam_score)
  141. common.save_cache(task, digest, rule, symbols, spam_score)
  142. else
  143. local symbols_table = lua_util.str_split(symbols, ",")
  144. lua_util.debugm(rule.N, task, '%s: returned symbols as table: %s', rule.log_prefix, symbols_table)
  145. common.yield_result(task, rule, symbols_table, spam_score)
  146. common.save_cache(task, digest, rule, symbols_table, spam_score)
  147. end
  148. else
  149. common.save_cache(task, digest, rule, 'OK')
  150. common.log_clean(task, rule, 'no spam detected - spam score: ' .. spam_score .. ', symbols: ' .. symbols)
  151. end
  152. end
  153. end
  154. tcp.request({
  155. task = task,
  156. host = addr:to_string(),
  157. port = addr:get_port(),
  158. timeout = rule['timeout'],
  159. data = request_data,
  160. callback = spamassassin_callback,
  161. })
  162. end
  163. if common.condition_check_and_continue(task, content, rule, digest, spamassassin_check_uncached) then
  164. return
  165. else
  166. spamassassin_check_uncached()
  167. end
  168. end
  169. return {
  170. type = {N,'spam', 'scanner'},
  171. description = 'spamassassin spam scanner',
  172. configure = spamassassin_config,
  173. check = spamassassin_check,
  174. name = N
  175. }