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.

rbl.lua 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. --[[
  2. Copyright (c) 2011-2015, Vsevolod Stakhov <vsevolod@highsecure.ru>
  3. Copyright (c) 2013-2015, Andrew Lewis <nerf@judo.za.org>
  4. All rights reserved.
  5. Redistribution and use in source and binary forms, with or without
  6. modification, are permitted provided that the following conditions are met:
  7. 1. Redistributions of source code must retain the above copyright notice, this
  8. list of conditions and the following disclaimer.
  9. 2. Redistributions in binary form must reproduce the above copyright notice,
  10. this list of conditions and the following disclaimer in the documentation
  11. and/or other materials provided with the distribution.
  12. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  13. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  14. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  15. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  16. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  17. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  18. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  19. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  20. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  21. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22. ]]--
  23. -- This plugin implements various types of RBL checks
  24. -- Documentation can be found here:
  25. -- https://rspamd.com/doc/modules/rbl.html
  26. local rbls = {}
  27. local local_exclusions = nil
  28. local private_ips = nil
  29. local rspamd_logger = require 'rspamd_logger'
  30. local rspamd_ip = require 'rspamd_ip'
  31. local function validate_dns(lstr)
  32. if lstr:match('%.%.') then
  33. return false
  34. end
  35. for v in lstr:gmatch('[^%.]+') do
  36. if not v:match('^[%w-]+$') or v:len() > 63
  37. or v:match('^-') or v:match('-$') then
  38. return false
  39. end
  40. end
  41. return true
  42. end
  43. local function is_private_ip(rip)
  44. if private_ips and private_ips:get_key(rip) then
  45. return true
  46. end
  47. return false
  48. end
  49. local function is_excluded_ip(rip)
  50. if local_exclusions and local_exclusions:get_key(rip) then
  51. return true
  52. end
  53. return false
  54. end
  55. local function ip_to_rbl(ip, rbl)
  56. return table.concat(ip:inversed_str_octets(), '.') .. '.' .. rbl
  57. end
  58. local function rbl_cb (task)
  59. local function rbl_dns_cb(resolver, to_resolve, results, err, key)
  60. if results then
  61. local thisrbl = nil
  62. for k,r in pairs(rbls) do
  63. if k == key then
  64. thisrbl = r
  65. break
  66. end
  67. end
  68. if thisrbl ~= nil then
  69. if thisrbl['returncodes'] == nil then
  70. if thisrbl['symbol'] ~= nil then
  71. task:insert_result(thisrbl['symbol'], 1)
  72. end
  73. else
  74. for _,result in pairs(results) do
  75. local ipstr = result:to_string()
  76. local foundrc = false
  77. for s,i in pairs(thisrbl['returncodes']) do
  78. if type(i) == 'string' then
  79. if string.find(ipstr, '^' .. i .. '$') then
  80. foundrc = true
  81. task:insert_result(s, 1)
  82. break
  83. end
  84. elseif type(i) == 'table' then
  85. for _,v in pairs(i) do
  86. if string.find(ipstr, '^' .. v .. '$') then
  87. foundrc = true
  88. task:insert_result(s, 1)
  89. break
  90. end
  91. end
  92. end
  93. end
  94. if not foundrc then
  95. if thisrbl['unknown'] and thisrbl['symbol'] then
  96. task:insert_result(thisrbl['symbol'], 1)
  97. else
  98. rspamd_logger.err('RBL ' .. thisrbl['rbl'] ..
  99. ' returned unknown result ' .. ipstr)
  100. end
  101. end
  102. end
  103. end
  104. end
  105. end
  106. task:inc_dns_req()
  107. end
  108. local havegot = {}
  109. local notgot = {}
  110. for k,rbl in pairs(rbls) do
  111. (function()
  112. if rbl['exclude_users'] then
  113. if not havegot['user'] and not notgot['user'] then
  114. havegot['user'] = task:get_user()
  115. if havegot['user'] == nil then
  116. notgot['user'] = true
  117. end
  118. end
  119. if havegot['user'] ~= nil then
  120. return
  121. end
  122. end
  123. if (rbl['exclude_local'] or rbl['exclude_private_ips']) and not notgot['from'] then
  124. if not havegot['from'] then
  125. havegot['from'] = task:get_from_ip()
  126. if not havegot['from']:is_valid() then
  127. notgot['from'] = true
  128. end
  129. end
  130. if havegot['from'] and not notgot['from'] and ((rbl['exclude_local'] and
  131. is_excluded_ip(havegot['from'])) or (rbl['exclude_private_ips'] and
  132. is_private_ip(havegot['from']))) then
  133. return
  134. end
  135. end
  136. if rbl['helo'] then
  137. (function()
  138. if notgot['helo'] then
  139. return
  140. end
  141. if not havegot['helo'] then
  142. havegot['helo'] = task:get_helo()
  143. if havegot['helo'] == nil or
  144. not validate_dns(havegot['helo']) then
  145. notgot['helo'] = true
  146. return
  147. end
  148. end
  149. task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
  150. havegot['helo'] .. '.' .. rbl['rbl'], rbl_dns_cb, k)
  151. end)()
  152. end
  153. if rbl['emails'] then
  154. (function()
  155. if notgot['emails'] then
  156. return
  157. end
  158. if not havegot['emails'] then
  159. havegot['emails'] = task:get_emails()
  160. if havegot['emails'] == nil then
  161. notgot['emails'] = true
  162. return
  163. end
  164. local cleanList = {}
  165. for _, e in pairs(havegot['emails']) do
  166. local localpart = e:get_user()
  167. local domainpart = e:get_host()
  168. if rbl['emails'] == 'domain_only' then
  169. if not cleanList[domainpart] and validate_dns(domainpart) then
  170. cleanList[domainpart] = true
  171. end
  172. else
  173. if validate_dns(localpart) and validate_dns(domainpart) then
  174. table.insert(cleanList, localpart .. '.' .. domainpart)
  175. end
  176. end
  177. end
  178. havegot['emails'] = cleanList
  179. if not next(havegot['emails']) then
  180. notgot['emails'] = true
  181. return
  182. end
  183. end
  184. if rbl['emails'] == 'domain_only' then
  185. for domain, _ in pairs(havegot['emails']) do
  186. task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
  187. domain .. '.' .. rbl['rbl'], rbl_dns_cb, k)
  188. end
  189. else
  190. for _, email in pairs(havegot['emails']) do
  191. task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
  192. email .. '.' .. rbl['rbl'], rbl_dns_cb, k)
  193. end
  194. end
  195. end)()
  196. end
  197. if rbl['rdns'] then
  198. (function()
  199. if notgot['rdns'] then
  200. return
  201. end
  202. if not havegot['rdns'] then
  203. havegot['rdns'] = task:get_hostname()
  204. if havegot['rdns'] == nil or havegot['rdns'] == 'unknown' then
  205. notgot['rdns'] = true
  206. return
  207. end
  208. end
  209. task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
  210. havegot['rdns'] .. '.' .. rbl['rbl'], rbl_dns_cb, k)
  211. end)()
  212. end
  213. if rbl['from'] then
  214. (function()
  215. if notgot['from'] then
  216. return
  217. end
  218. if not havegot['from'] then
  219. havegot['from'] = task:get_from_ip()
  220. if not havegot['from']:is_valid() then
  221. notgot['from'] = true
  222. return
  223. end
  224. end
  225. if (havegot['from']:get_version() == 6 and rbl['ipv6']) or
  226. (havegot['from']:get_version() == 4 and rbl['ipv4']) then
  227. task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
  228. ip_to_rbl(havegot['from'], rbl['rbl']), rbl_dns_cb, k)
  229. end
  230. end)()
  231. end
  232. if rbl['received'] then
  233. (function()
  234. if notgot['received'] then
  235. return
  236. end
  237. if not havegot['received'] then
  238. havegot['received'] = task:get_received_headers()
  239. if next(havegot['received']) == nil then
  240. notgot['received'] = true
  241. return
  242. end
  243. end
  244. for _,rh in ipairs(havegot['received']) do
  245. if rh['real_ip'] and rh['real_ip']:is_valid() then
  246. if ((rh['real_ip']:get_version() == 6 and rbl['ipv6']) or
  247. (rh['real_ip']:get_version() == 4 and rbl['ipv4'])) and
  248. ((rbl['exclude_private_ips'] and not is_private_ip(rh['real_ip'])) or
  249. not rbl['exclude_private_ips']) and ((rbl['exclude_local_ips'] and
  250. not is_excluded_ip(rh['real_ip'])) or not rbl['exclude_local_ips']) then
  251. task:get_resolver():resolve_a(task:get_session(), task:get_mempool(),
  252. ip_to_rbl(rh['real_ip'], rbl['rbl']), rbl_dns_cb, k)
  253. end
  254. end
  255. end
  256. end)()
  257. end
  258. end)()
  259. end
  260. end
  261. -- Registration
  262. if type(rspamd_config.get_api_version) ~= 'nil' then
  263. if rspamd_config:get_api_version() >= 1 then
  264. rspamd_config:register_module_option('rbl', 'rbls', 'map')
  265. rspamd_config:register_module_option('rbl', 'default_ipv4', 'string')
  266. rspamd_config:register_module_option('rbl', 'default_ipv6', 'string')
  267. rspamd_config:register_module_option('rbl', 'default_received', 'string')
  268. rspamd_config:register_module_option('rbl', 'default_from', 'string')
  269. rspamd_config:register_module_option('rbl', 'default_rdns', 'string')
  270. rspamd_config:register_module_option('rbl', 'default_helo', 'string')
  271. rspamd_config:register_module_option('rbl', 'default_unknown', 'string')
  272. rspamd_config:register_module_option('rbl', 'default_exclude_users', 'string')
  273. rspamd_config:register_module_option('rbl', 'default_exclude_private_ips', 'string')
  274. rspamd_config:register_module_option('rbl', 'local_exclude_ip_map', 'string')
  275. rspamd_config:register_module_option('rbl', 'default_exclude_local', 'string')
  276. rspamd_config:register_module_option('rbl', 'private_ips', 'string')
  277. rspamd_config:register_module_option('rbl', 'default_emails', 'string')
  278. rspamd_config:register_module_option('rbl', 'default_is_whitelist', 'string')
  279. rspamd_config:register_module_option('rbl', 'default_ignore_whitelists', 'string')
  280. end
  281. end
  282. -- Configuration
  283. local opts = rspamd_config:get_all_opt('rbl')
  284. if not opts or type(opts) ~= 'table' then
  285. return
  286. end
  287. -- Plugin defaults should not be changed - override these in config
  288. -- New defaults should not alter behaviour
  289. default_defaults = {
  290. ['default_ipv4'] = {[1] = true, [2] = 'ipv4'},
  291. ['default_ipv6'] = {[1] = false, [2] = 'ipv6'},
  292. ['default_received'] = {[1] = true, [2] = 'received'},
  293. ['default_from'] = {[1] = false, [2] = 'from'},
  294. ['default_unknown'] = {[1] = false, [2] = 'unknown'},
  295. ['default_rdns'] = {[1] = false, [2] = 'rdns'},
  296. ['default_helo'] = {[1] = false, [2] = 'helo'},
  297. ['default_emails'] = {[1] = false, [2] = 'emails'},
  298. ['default_exclude_users'] = {[1] = false, [2] = 'exclude_users'},
  299. ['default_exclude_private_ips'] = {[1] = true, [2] = 'exclude_private_ips'},
  300. ['default_exclude_users'] = {[1] = false, [2] = 'exclude_users'},
  301. ['default_exclude_local'] = {[1] = true, [2] = 'exclude_local'},
  302. ['default_is_whitelist'] = {[1] = false, [2] = 'is_whitelist'},
  303. ['default_ignore_whitelist'] = {[1] = false, [2] = 'ignore_whitelists'},
  304. }
  305. for default, default_v in pairs(default_defaults) do
  306. if opts[default] == nil then
  307. opts[default] = default_v[1]
  308. end
  309. end
  310. if(opts['local_exclude_ip_map'] ~= nil) then
  311. local_exclusions = rspamd_config:add_radix_map(opts['local_exclude_ip_map'])
  312. end
  313. if(opts['private_ips'] ~= nil) then
  314. private_ips = rspamd_config:radix_from_config('rbl', 'private_ips')
  315. end
  316. local white_symbols = {}
  317. local black_symbols = {}
  318. for key,rbl in pairs(opts['rbls']) do
  319. for default, default_v in pairs(default_defaults) do
  320. if(rbl[default_v[2]] == nil) then
  321. rbl[default_v[2]] = opts[default]
  322. end
  323. end
  324. if type(rbl['returncodes']) == 'table' then
  325. for s,_ in pairs(rbl['returncodes']) do
  326. if type(rspamd_config.get_api_version) ~= 'nil' then
  327. rspamd_config:register_virtual_symbol(s, 1)
  328. if(rbl['is_whitelist']) then
  329. if type(rbl['whitelist_exception']) == 'string' then
  330. if (rbl['whitelist_exception'] ~= s) then
  331. table.insert(white_symbols, s)
  332. end
  333. elseif type(rbl['whitelist_exception']) == 'table' then
  334. local foundException = false
  335. for _, e in pairs(rbl['whitelist_exception']) do
  336. if e == s then
  337. foundException = true
  338. break
  339. end
  340. end
  341. if not foundException then
  342. table.insert(white_symbols, s)
  343. end
  344. end
  345. else
  346. table.insert(black_symbols, s)
  347. end
  348. end
  349. end
  350. end
  351. if not rbl['symbol'] and type(rbl['returncodes']) ~= 'nil' and not rbl['unknown'] then
  352. rbl['symbol'] = key
  353. end
  354. if type(rspamd_config.get_api_version) ~= 'nil' and rbl['symbol'] then
  355. rspamd_config:register_virtual_symbol(rbl['symbol'], 1)
  356. if(rbl['is_whitelist']) then
  357. table.insert(white_symbols, rbl['symbol'])
  358. else
  359. if rbl['ignore_whitelists'] == false then
  360. table.insert(black_symbols, rbl['symbol'])
  361. end
  362. end
  363. end
  364. rbls[key] = rbl
  365. end
  366. for _, w in pairs(white_symbols) do
  367. for _, b in pairs(black_symbols) do
  368. csymbol = 'RBL_COMPOSITE_' .. w .. '_' .. b
  369. rspamd_config:set_metric_symbol(csymbol, 0, 'Autogenerated composite')
  370. rspamd_config:add_composite(csymbol, w .. ' & ' .. b)
  371. rspamd_config:register_virtual_symbol(csymbol, 1)
  372. end
  373. end
  374. rspamd_config:register_callback_symbol_priority('RBL', 1.0, 0, rbl_cb)