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 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  3. Copyright (c) 2013-2015, Andrew Lewis <nerf@judo.za.org>
  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. if confighelp then
  15. return
  16. end
  17. local hash = require 'rspamd_cryptobox_hash'
  18. local rspamd_logger = require 'rspamd_logger'
  19. local rspamd_util = require 'rspamd_util'
  20. local rspamd_ip = require "rspamd_ip"
  21. local fun = require 'fun'
  22. local lua_util = require 'lua_util'
  23. local selectors = require "lua_selectors"
  24. local bit = require 'bit'
  25. local lua_maps = require "lua_maps"
  26. local rbl_common = require "plugins/rbl"
  27. local rspamd_url = require "rspamd_url"
  28. -- This plugin implements various types of RBL checks
  29. -- Documentation can be found here:
  30. -- https://rspamd.com/doc/modules/rbl.html
  31. local E = {}
  32. local N = 'rbl'
  33. -- Checks that could be performed by rbl module
  34. local local_exclusions
  35. local white_symbols = {}
  36. local black_symbols = {}
  37. local monitored_addresses = {}
  38. local known_selectors = {} -- map from selector string to selector id
  39. local url_flag_bits = rspamd_url.flags
  40. local function get_monitored(rbl)
  41. local function is_random_monitored()
  42. -- Explicit definition
  43. if type(rbl.random_monitored) == 'boolean' then
  44. return rbl.random_monitored
  45. end
  46. -- We check 127.0.0.1 for merely RBLs with `from` or `received` and only if
  47. -- they don't have `no_ip` attribute at the same time
  48. --
  49. -- Convert to a boolean variable using the common idiom
  50. return (not (rbl.from or rbl.received)
  51. or rbl.no_ip)
  52. and true or false
  53. end
  54. local default_monitored = '1.0.0.127'
  55. local ret = {
  56. rcode = 'nxdomain',
  57. prefix = default_monitored,
  58. random = is_random_monitored(),
  59. }
  60. if rbl.monitored_address then
  61. ret.prefix = rbl.monitored_address
  62. end
  63. lua_util.debugm(N, rspamd_config,
  64. 'added monitored address: %s (%s random)',
  65. ret.prefix, ret.random)
  66. return ret
  67. end
  68. local function validate_dns(lstr)
  69. if lstr:match('%.%.') then
  70. -- two dots in a row
  71. return false, "two dots in a row"
  72. end
  73. if not rspamd_util.is_valid_utf8(lstr) then
  74. -- invalid utf8 detected
  75. return false, "invalid utf8"
  76. end
  77. for v in lstr:gmatch('[^%.]+') do
  78. if v:len() > 63 then
  79. -- too long label
  80. return false, "too long label"
  81. end
  82. if v:match('^-') or v:match('-$') then
  83. -- dash at the beginning or end of label
  84. return false, "dash at the beginning or end of label"
  85. end
  86. end
  87. return true
  88. end
  89. local function maybe_make_hash(data, rule)
  90. if rule.hash then
  91. local h = hash.create_specific(rule.hash, data)
  92. local s
  93. if rule.hash_format then
  94. if rule.hash_format == 'base32' then
  95. s = h:base32()
  96. elseif rule.hash_format == 'base64' then
  97. s = h:base64()
  98. else
  99. s = h:hex()
  100. end
  101. else
  102. s = h:hex()
  103. end
  104. if rule.hash_len then
  105. s = s:sub(1, rule.hash_len)
  106. end
  107. return s
  108. else
  109. return data
  110. end
  111. end
  112. local function is_excluded_ip(rip)
  113. if local_exclusions and local_exclusions:get_key(rip) then
  114. return true
  115. end
  116. return false
  117. end
  118. local function ip_to_rbl(ip)
  119. return table.concat(ip:inversed_str_octets(), '.')
  120. end
  121. local function gen_check_rcvd_conditions(rbl, received_total)
  122. local min_pos = tonumber(rbl.received_min_pos)
  123. local max_pos = tonumber(rbl.received_max_pos)
  124. local match_flags = rbl.received_flags
  125. local nmatch_flags = rbl.received_nflags
  126. local function basic_received_check(rh)
  127. if not (rh.real_ip and rh.real_ip:is_valid()) then
  128. return false
  129. end
  130. if ((rh.real_ip:get_version() == 6 and rbl.ipv6) or
  131. (rh.real_ip:get_version() == 4 and rbl.ipv4)) and
  132. ((rbl.exclude_local and not rh.real_ip:is_local() or is_excluded_ip(rh.real_ip)) or not rbl.exclude_local) then
  133. return true
  134. else
  135. return false
  136. end
  137. end
  138. local function positioned_received_check(rh, pos)
  139. if not rh or not basic_received_check(rh) then
  140. return false
  141. end
  142. local got_flags = rh.flags or E
  143. if min_pos then
  144. if min_pos < 0 then
  145. if min_pos == -1 then
  146. if (pos ~= received_total) then
  147. return false
  148. end
  149. else
  150. if pos <= (received_total - math.abs(min_pos)) then
  151. return false
  152. end
  153. end
  154. elseif pos < min_pos then
  155. return false
  156. end
  157. end
  158. if max_pos then
  159. if max_pos < -1 then
  160. if (received_total - math.abs(max_pos)) >= pos then
  161. return false
  162. end
  163. elseif max_pos > 0 then
  164. if pos > max_pos then
  165. return false
  166. end
  167. end
  168. end
  169. if match_flags then
  170. for _, flag in ipairs(match_flags) do
  171. if not got_flags[flag] then
  172. return false
  173. end
  174. end
  175. end
  176. if nmatch_flags then
  177. for _, flag in ipairs(nmatch_flags) do
  178. if got_flags[flag] then
  179. return false
  180. end
  181. end
  182. end
  183. return true
  184. end
  185. if not (max_pos or min_pos or match_flags or nmatch_flags) then
  186. return basic_received_check
  187. else
  188. return positioned_received_check
  189. end
  190. end
  191. local matchers = {}
  192. matchers.radix = function(_, _, real_ip, map)
  193. return map and map:get_key(real_ip) or false
  194. end
  195. matchers.equality = function(codes, to_match)
  196. if type(codes) ~= 'table' then return codes == to_match end
  197. for _, ip in ipairs(codes) do
  198. if to_match == ip then
  199. return true
  200. end
  201. end
  202. return false
  203. end
  204. matchers.luapattern = function(codes, to_match)
  205. if type(codes) ~= 'table' then
  206. return string.find(to_match, '^' .. codes .. '$') and true or false
  207. end
  208. for _, pattern in ipairs(codes) do
  209. if string.find(to_match, '^' .. pattern .. '$') then
  210. return true
  211. end
  212. end
  213. return false
  214. end
  215. matchers.regexp = function(_, to_match, _, map)
  216. return map and map:get_key(to_match) or false
  217. end
  218. matchers.glob = function(_, to_match, _, map)
  219. return map and map:get_key(to_match) or false
  220. end
  221. local function rbl_dns_process(task, rbl, to_resolve, results, err, resolve_table_elt, match)
  222. local function make_option(ip, label)
  223. if ip then
  224. return string.format('%s:%s:%s',
  225. resolve_table_elt.orig,
  226. label,
  227. ip)
  228. else
  229. return string.format('%s:%s',
  230. resolve_table_elt.orig,
  231. label)
  232. end
  233. end
  234. local function insert_result(s, ip, label)
  235. if rbl.symbols_prefixes then
  236. local prefix = rbl.symbols_prefixes[label]
  237. if not prefix then
  238. rspamd_logger.warnx(task, 'unlisted symbol prefix for %s', label)
  239. task:insert_result(s, 1.0, make_option(ip, label))
  240. else
  241. task:insert_result(prefix .. '_' .. s, 1.0, make_option(ip, label))
  242. end
  243. else
  244. task:insert_result(s, 1.0, make_option(ip, label))
  245. end
  246. end
  247. local function insert_results(s, ip)
  248. for label in pairs(resolve_table_elt.what) do
  249. insert_result(s, ip, label)
  250. end
  251. end
  252. if err and (err ~= 'requested record is not found' and
  253. err ~= 'no records with this name') then
  254. rspamd_logger.infox(task, 'error looking up %s: %s', to_resolve, err)
  255. task:insert_result(rbl.symbol .. '_FAIL', 1, string.format('%s:%s',
  256. resolve_table_elt.orig, err))
  257. return
  258. end
  259. if not results then
  260. lua_util.debugm(N, task,
  261. 'DNS RESPONSE: label=%1 results=%2 error=%3 rbl=%4',
  262. to_resolve, false, err, rbl.symbol)
  263. return
  264. else
  265. lua_util.debugm(N, task,
  266. 'DNS RESPONSE: label=%1 results=%2 error=%3 rbl=%4',
  267. to_resolve, true, err, rbl.symbol)
  268. end
  269. if rbl.returncodes == nil and rbl.returnbits == nil and rbl.symbol ~= nil then
  270. insert_results(rbl.symbol)
  271. return
  272. end
  273. local returncodes_maps = rbl.returncodes_maps or {}
  274. for _, result in ipairs(results) do
  275. local ipstr = result:to_string()
  276. lua_util.debugm(N, task, '%s DNS result %s', to_resolve, ipstr)
  277. local foundrc = false
  278. -- Check return codes
  279. if rbl.returnbits then
  280. local ipnum = result:to_number()
  281. for s, bits in pairs(rbl.returnbits) do
  282. for _, check_bit in ipairs(bits) do
  283. if bit.band(ipnum, check_bit) == check_bit then
  284. foundrc = true
  285. insert_results(s)
  286. -- Here, we continue with other bits
  287. end
  288. end
  289. end
  290. elseif rbl.returncodes then
  291. for s, codes in pairs(rbl.returncodes) do
  292. local res = match(codes, ipstr, result, returncodes_maps[s])
  293. if res then
  294. foundrc = true
  295. insert_results(s)
  296. end
  297. end
  298. end
  299. if not foundrc then
  300. if rbl.unknown and rbl.symbol then
  301. insert_results(rbl.symbol, ipstr)
  302. else
  303. lua_util.debugm(N, task, '%1 returned unknown result: %2',
  304. to_resolve, ipstr)
  305. end
  306. end
  307. end
  308. end
  309. local function gen_rbl_callback(rule)
  310. local function is_whitelisted(task, req, req_str, whitelist, what)
  311. if rule.ignore_whitelist then
  312. lua_util.debugm(N, task,
  313. 'ignore whitelisting checks to %s by %s: ignore whitelist is being set',
  314. req_str, rule.symbol)
  315. return false
  316. end
  317. if rule.whitelist then
  318. if rule.whitelist:get_key(req) then
  319. lua_util.debugm(N, task,
  320. 'whitelisted %s on %s',
  321. req_str, rule.symbol)
  322. return true
  323. end
  324. end
  325. -- Maybe whitelisted by some other rbl rule
  326. if whitelist then
  327. local wl = whitelist[req_str]
  328. if wl then
  329. lua_util.debugm(N, task,
  330. 'whitelisted request to %s by %s (%s) rbl rule (%s checked type, %s whitelist type)',
  331. req_str, wl.type, wl.symbol, what, wl.type)
  332. if wl.type == what then
  333. -- This was decided to be a bad idea as in case of whitelisting a request to blacklist
  334. -- is not even sent
  335. --task:adjust_result(wl.symbol, 0.0 / 0.0, rule.symbol)
  336. return true
  337. end
  338. end
  339. end
  340. return false
  341. end
  342. local function add_dns_request(task, req, forced, is_ip, requests_table, label, whitelist)
  343. local req_str = req
  344. if is_ip then
  345. req_str = tostring(req)
  346. end
  347. if whitelist and is_whitelisted(task, req, req_str, whitelist, label) then
  348. return
  349. end
  350. if is_ip then
  351. req = ip_to_rbl(req)
  352. end
  353. if requests_table[req] then
  354. -- Duplicate request
  355. local nreq = requests_table[req]
  356. if forced and not nreq.forced then
  357. nreq.forced = true
  358. end
  359. if not nreq.what[label] then
  360. nreq.what[label] = true
  361. end
  362. return true, nreq -- Duplicate
  363. else
  364. local nreq
  365. local resolve_ip = rule.resolve_ip and not is_ip
  366. if rule.process_script then
  367. local processed = rule.process_script(req, rule.rbl, task, resolve_ip)
  368. if processed then
  369. nreq = {
  370. forced = forced,
  371. n = processed,
  372. orig = req_str,
  373. resolve_ip = resolve_ip,
  374. what = { [label] = true },
  375. }
  376. requests_table[req] = nreq
  377. end
  378. else
  379. local to_resolve
  380. local origin = req
  381. if not resolve_ip then
  382. origin = maybe_make_hash(req, rule)
  383. to_resolve = string.format('%s.%s',
  384. origin,
  385. rule.rbl)
  386. else
  387. -- First, resolve origin stuff without hashing or anything
  388. to_resolve = origin
  389. end
  390. nreq = {
  391. forced = forced,
  392. n = to_resolve,
  393. orig = req_str,
  394. resolve_ip = resolve_ip,
  395. what = { [label] = true },
  396. }
  397. requests_table[req] = nreq
  398. end
  399. return false, nreq
  400. end
  401. end
  402. -- Here, we have functional approach: we form a pipeline of functions
  403. -- f1, f2, ... fn. Each function accepts task and return boolean value
  404. -- that allows to process pipeline further
  405. -- Each function in the pipeline can add something to `dns_req` vector as a side effect
  406. local function is_alive(_, _)
  407. if rule.monitored then
  408. if not rule.monitored:alive() then
  409. return false
  410. end
  411. end
  412. return true
  413. end
  414. local function check_required_symbols(task, _)
  415. if rule.require_symbols then
  416. return fun.all(function(sym)
  417. task:has_symbol(sym)
  418. end, rule.require_symbols)
  419. end
  420. return true
  421. end
  422. local function check_user(task, _)
  423. if task:get_user() then
  424. return false
  425. end
  426. return true
  427. end
  428. local function check_local(task, _)
  429. local ip = task:get_from_ip()
  430. if ip and not ip:is_valid() then
  431. ip = nil
  432. end
  433. if ip and ip:is_local() or is_excluded_ip(ip) then
  434. return false
  435. end
  436. return true
  437. end
  438. local function check_helo(task, requests_table, whitelist)
  439. local helo = task:get_helo()
  440. if not helo then
  441. -- Avoid pipeline breaking
  442. return true
  443. end
  444. add_dns_request(task, helo, true, false, requests_table,
  445. 'helo', whitelist)
  446. return true
  447. end
  448. local function check_dkim(task, requests_table, whitelist)
  449. local das = task:get_symbol('DKIM_TRACE')
  450. local mime_from_domain
  451. if das and das[1] and das[1].options then
  452. if rule.dkim_match_from then
  453. -- We check merely mime from
  454. mime_from_domain = ((task:get_from('mime') or E)[1] or E).domain
  455. if mime_from_domain then
  456. local mime_from_domain_tld = rule.url_full_hostname and
  457. mime_from_domain or rspamd_util.get_tld(mime_from_domain)
  458. if rule.url_compose_map then
  459. mime_from_domain = rule.url_compose_map:process_url(task, mime_from_domain_tld, mime_from_domain)
  460. else
  461. mime_from_domain = mime_from_domain_tld
  462. end
  463. end
  464. end
  465. for _, d in ipairs(das[1].options) do
  466. local domain, result = d:match('^([^%:]*):([%+%-%~])$')
  467. -- We must ignore bad signatures, omg
  468. if domain and result and result == '+' then
  469. if rule.dkim_match_from then
  470. -- We check merely mime from
  471. local domain_tld = domain
  472. if not rule.dkim_domainonly then
  473. -- Adjust
  474. domain_tld = rspamd_util.get_tld(domain)
  475. if rule.url_compose_map then
  476. domain_tld = rule.url_compose_map:process_url(task, domain_tld, domain)
  477. elseif rule.url_full_hostname then
  478. domain_tld = domain
  479. end
  480. end
  481. if mime_from_domain and mime_from_domain == domain_tld then
  482. add_dns_request(task, domain_tld, true, false, requests_table,
  483. 'dkim', whitelist)
  484. end
  485. else
  486. if rule.dkim_domainonly then
  487. local domain_tld = rspamd_util.get_tld(domain)
  488. if rule.url_compose_map then
  489. domain_tld = rule.url_compose_map:process_url(task, domain_tld, domain)
  490. elseif rule.url_full_hostname then
  491. domain_tld = domain
  492. end
  493. add_dns_request(task, domain_tld,
  494. false, false, requests_table, 'dkim', whitelist)
  495. else
  496. add_dns_request(task, domain, false, false, requests_table,
  497. 'dkim', whitelist)
  498. end
  499. end
  500. end
  501. end
  502. end
  503. return true
  504. end
  505. local function check_urls(task, requests_table, whitelist)
  506. local esld_lim = 1
  507. if rule.url_compose_map then
  508. esld_lim = nil -- Avoid esld limit as we use custom composition rules
  509. end
  510. local ex_params = {
  511. task = task,
  512. limit = rule.requests_limit,
  513. ignore_redirected = true,
  514. ignore_ip = rule.no_ip,
  515. need_images = rule.images,
  516. need_emails = false,
  517. need_content = rule.content_urls or false,
  518. esld_limit = esld_lim,
  519. no_cache = true,
  520. }
  521. if rule.numeric_urls then
  522. if rule.content_urls then
  523. if not rule.images then
  524. ex_params.flags_mode = 'explicit'
  525. ex_params.flags = { 'numeric' }
  526. ex_params.filter = function(url)
  527. return (bit.band(url:get_flags_num(), url_flag_bits.image) == 0)
  528. end
  529. else
  530. ex_params.filter = function(url)
  531. return (bit.band(url:get_flags_num(), url_flag_bits.numeric) ~= 0)
  532. end
  533. end
  534. elseif rule.images then
  535. ex_params.filter = function(url)
  536. return (bit.band(url:get_flags_num(), url_flag_bits.numeric) ~= 0)
  537. end
  538. else
  539. ex_params.flags_mode = 'explicit'
  540. ex_params.flags = { 'numeric' }
  541. ex_params.filter = function(url)
  542. return (bit.band(url:get_flags_num(), url_flag_bits.content) == 0)
  543. end
  544. end
  545. elseif not rule.urls and (rule.content_urls or rule.images) then
  546. ex_params.flags_mode = 'explicit'
  547. ex_params.flags = {}
  548. if rule.content_urls then
  549. table.insert(ex_params.flags, 'content')
  550. end
  551. if rule.images then
  552. table.insert(ex_params.flags, 'image')
  553. end
  554. end
  555. local urls = lua_util.extract_specific_urls(ex_params)
  556. for _, u in ipairs(urls) do
  557. local flags = u:get_flags_num()
  558. if bit.band(flags, url_flag_bits.numeric) ~= 0 then
  559. -- For numeric urls we convert data to the ip address and
  560. -- reverse octets. See #3948 for details
  561. local to_resolve = u:get_host()
  562. local addr = rspamd_ip.from_string(to_resolve)
  563. if addr then
  564. to_resolve = table.concat(addr:inversed_str_octets(), ".")
  565. end
  566. add_dns_request(task, to_resolve, false,
  567. false, requests_table, 'url', whitelist)
  568. else
  569. local url_hostname = u:get_host()
  570. local url_tld = rule.url_full_hostname and url_hostname or u:get_tld()
  571. if rule.url_compose_map then
  572. url_tld = rule.url_compose_map:process_url(task, url_tld, url_hostname)
  573. end
  574. add_dns_request(task, url_tld, false,
  575. false, requests_table, 'url', whitelist)
  576. end
  577. end
  578. return true
  579. end
  580. local function check_from(task, requests_table, whitelist)
  581. local ip = task:get_from_ip()
  582. if not ip or not ip:is_valid() then
  583. return true
  584. end
  585. if (ip:get_version() == 6 and rule.ipv6) or
  586. (ip:get_version() == 4 and rule.ipv4) then
  587. add_dns_request(task, ip, true, true,
  588. requests_table, 'from',
  589. whitelist)
  590. end
  591. return true
  592. end
  593. local function check_received(task, requests_table, whitelist)
  594. local received = fun .filter(function(h)
  595. return not h['flags']['artificial']
  596. end, task:get_received_headers()):totable()
  597. local received_total = #received
  598. local check_conditions = gen_check_rcvd_conditions(rule, received_total)
  599. for pos, rh in ipairs(received) do
  600. if check_conditions(rh, pos) then
  601. add_dns_request(task, rh.real_ip, false, true,
  602. requests_table, 'received',
  603. whitelist)
  604. end
  605. end
  606. return true
  607. end
  608. local function check_rdns(task, requests_table, whitelist)
  609. local hostname = task:get_hostname()
  610. if hostname == nil or hostname == 'unknown' then
  611. return true
  612. end
  613. add_dns_request(task, hostname, true, false,
  614. requests_table, 'rdns', whitelist)
  615. return true
  616. end
  617. local function check_selector(task, requests_table, whitelist)
  618. for selector_label, selector in pairs(rule.selectors) do
  619. local res = selector(task)
  620. if res and type(res) == 'table' then
  621. for _, r in ipairs(res) do
  622. add_dns_request(task, r, false, false, requests_table,
  623. selector_label, whitelist)
  624. end
  625. elseif res then
  626. add_dns_request(task, res, false, false,
  627. requests_table, selector_label, whitelist)
  628. end
  629. end
  630. return true
  631. end
  632. local function check_email_table(task, email_tbl, requests_table, whitelist, what)
  633. lua_util.remove_email_aliases(email_tbl)
  634. email_tbl.domain = email_tbl.domain:lower()
  635. email_tbl.user = email_tbl.user:lower()
  636. if email_tbl.domain == '' or email_tbl.user == '' then
  637. rspamd_logger.infox(task, "got an email with some empty parts: '%s@%s'; skip it in the checks",
  638. email_tbl.user, email_tbl.domain)
  639. return
  640. end
  641. if rule.emails_domainonly then
  642. add_dns_request(task, email_tbl.domain, false, false, requests_table,
  643. what, whitelist)
  644. else
  645. -- Also check WL for domain only
  646. if is_whitelisted(task,
  647. email_tbl.domain,
  648. email_tbl.domain,
  649. whitelist,
  650. what) then
  651. return
  652. end
  653. local delimiter = '.'
  654. if rule.emails_delimiter then
  655. delimiter = rule.emails_delimiter
  656. else
  657. if rule.hash then
  658. delimiter = '@'
  659. end
  660. end
  661. add_dns_request(task, string.format('%s%s%s',
  662. email_tbl.user, delimiter, email_tbl.domain), false, false,
  663. requests_table, what, whitelist)
  664. end
  665. end
  666. local function check_emails(task, requests_table, whitelist)
  667. local ex_params = {
  668. task = task,
  669. limit = rule.requests_limit,
  670. filter = function(u)
  671. return u:get_protocol() == 'mailto'
  672. end,
  673. need_emails = true,
  674. prefix = 'rbl_email'
  675. }
  676. if rule.emails_domainonly then
  677. if not rule.url_compose_map then
  678. ex_params.esld_limit = 1
  679. end
  680. ex_params.prefix = 'rbl_email_domainonly'
  681. end
  682. local emails = lua_util.extract_specific_urls(ex_params)
  683. for _, email in ipairs(emails) do
  684. local domain
  685. if rule.emails_domainonly and not rule.url_full_hostname then
  686. if rule.url_compose_map then
  687. domain = rule.url_compose_map:process_url(task, email:get_tld(), email:get_host())
  688. else
  689. domain = email:get_tld()
  690. end
  691. else
  692. domain = email:get_host()
  693. end
  694. local email_tbl = {
  695. domain = domain or '',
  696. user = email:get_user() or '',
  697. addr = tostring(email),
  698. }
  699. check_email_table(task, email_tbl, requests_table, whitelist, 'email')
  700. end
  701. return true
  702. end
  703. local function check_replyto(task, requests_table, whitelist)
  704. local function get_raw_header(name)
  705. return ((task:get_header_full(name) or {})[1] or {})['value']
  706. end
  707. local replyto = get_raw_header('Reply-To')
  708. if replyto then
  709. local rt = rspamd_util.parse_mail_address(replyto, task:get_mempool())
  710. lua_util.debugm(N, task, 'check replyto %s', rt[1])
  711. if rt and rt[1] and (rt[1].addr and #rt[1].addr > 0) then
  712. check_email_table(task, rt[1], requests_table, whitelist, 'replyto')
  713. end
  714. end
  715. return true
  716. end
  717. -- Create function pipeline depending on rbl settings
  718. local pipeline = {
  719. is_alive, -- check monitored status
  720. check_required_symbols -- if we have require_symbols then check those symbols
  721. }
  722. local description = {
  723. 'alive',
  724. }
  725. if rule.exclude_users then
  726. pipeline[#pipeline + 1] = check_user
  727. description[#description + 1] = 'user'
  728. end
  729. if rule.exclude_local then
  730. pipeline[#pipeline + 1] = check_local
  731. description[#description + 1] = 'local'
  732. end
  733. if rule.helo then
  734. pipeline[#pipeline + 1] = check_helo
  735. description[#description + 1] = 'helo'
  736. end
  737. if rule.dkim then
  738. pipeline[#pipeline + 1] = check_dkim
  739. description[#description + 1] = 'dkim'
  740. end
  741. if rule.emails then
  742. pipeline[#pipeline + 1] = check_emails
  743. description[#description + 1] = 'emails'
  744. end
  745. if rule.replyto then
  746. pipeline[#pipeline + 1] = check_replyto
  747. description[#description + 1] = 'replyto'
  748. end
  749. if rule.urls or rule.content_urls or rule.images or rule.numeric_urls then
  750. pipeline[#pipeline + 1] = check_urls
  751. description[#description + 1] = 'urls'
  752. end
  753. if rule.from then
  754. pipeline[#pipeline + 1] = check_from
  755. description[#description + 1] = 'ip'
  756. end
  757. if rule.received then
  758. pipeline[#pipeline + 1] = check_received
  759. description[#description + 1] = 'received'
  760. end
  761. if rule.rdns then
  762. pipeline[#pipeline + 1] = check_rdns
  763. description[#description + 1] = 'rdns'
  764. end
  765. if rule.selector then
  766. pipeline[#pipeline + 1] = check_selector
  767. description[#description + 1] = 'selector'
  768. end
  769. if not rule.returncodes_matcher then
  770. rule.returncodes_matcher = 'equality'
  771. end
  772. local match = matchers[rule.returncodes_matcher]
  773. local callback_f = function(task)
  774. -- DNS requests to issue (might be hashed afterwards)
  775. local dns_req = {}
  776. local whitelist = task:cache_get('rbl_whitelisted') or {}
  777. local function gen_rbl_dns_callback(resolve_table_elt)
  778. return function(_, to_resolve, results, err)
  779. rbl_dns_process(task, rule, to_resolve, results, err, resolve_table_elt, match)
  780. end
  781. end
  782. -- Execute functions pipeline
  783. for i, f in ipairs(pipeline) do
  784. if not f(task, dns_req, whitelist) then
  785. lua_util.debugm(N, task,
  786. "skip rbl check: %s; pipeline condition %s returned false",
  787. rule.symbol, i)
  788. return
  789. end
  790. end
  791. -- Now check all DNS requests pending and emit them
  792. local r = task:get_resolver()
  793. -- Used for 2 passes ip resolution
  794. local resolved_req = {}
  795. local nresolved = 0
  796. -- This is called when doing resolve_ip phase...
  797. local function gen_rbl_ip_dns_callback(orig_resolve_table_elt)
  798. return function(_, _, results, err)
  799. if not err then
  800. for _, dns_res in ipairs(results) do
  801. -- Check if we have rspamd{ip} userdata
  802. if type(dns_res) == 'userdata' then
  803. -- Add result as an actual RBL request
  804. local label = next(orig_resolve_table_elt.what)
  805. local dup, nreq = add_dns_request(task, dns_res, false, true,
  806. resolved_req, label)
  807. -- Add original name
  808. if not dup then
  809. nreq.orig = nreq.orig .. ':' .. orig_resolve_table_elt.n
  810. end
  811. end
  812. end
  813. end
  814. nresolved = nresolved - 1
  815. if nresolved == 0 then
  816. -- Emit real RBL requests as there are no ip resolution requests
  817. for name, req in pairs(resolved_req) do
  818. local val_res, val_error = validate_dns(req.n)
  819. if val_res then
  820. lua_util.debugm(N, task, "rbl %s; resolve %s -> %s",
  821. rule.symbol, name, req.n)
  822. r:resolve_a({
  823. task = task,
  824. name = req.n,
  825. callback = gen_rbl_dns_callback(req),
  826. forced = req.forced
  827. })
  828. else
  829. rspamd_logger.warnx(task, 'cannot send invalid DNS request %s for %s: %s',
  830. req.n, rule.symbol, val_error)
  831. end
  832. end
  833. end
  834. end
  835. end
  836. for name, req in pairs(dns_req) do
  837. local val_res, val_error = validate_dns(req.n)
  838. if val_res then
  839. lua_util.debugm(N, task, "rbl %s; resolve %s -> %s",
  840. rule.symbol, name, req.n)
  841. if req.resolve_ip then
  842. -- Deal with both ipv4 and ipv6
  843. -- Resolve names first
  844. if r:resolve_a({
  845. task = task,
  846. name = req.n,
  847. callback = gen_rbl_ip_dns_callback(req),
  848. forced = req.forced
  849. }) then
  850. nresolved = nresolved + 1
  851. end
  852. if r:resolve('aaaa', {
  853. task = task,
  854. name = req.n,
  855. callback = gen_rbl_ip_dns_callback(req),
  856. forced = req.forced
  857. }) then
  858. nresolved = nresolved + 1
  859. end
  860. else
  861. r:resolve_a({
  862. task = task,
  863. name = req.n,
  864. callback = gen_rbl_dns_callback(req),
  865. forced = req.forced
  866. })
  867. end
  868. else
  869. rspamd_logger.warnx(task, 'cannot send invalid DNS request %s for %s: %s',
  870. req.n, rule.symbol, val_error)
  871. end
  872. end
  873. end
  874. return callback_f, string.format('checks: %s', table.concat(description, ','))
  875. end
  876. local map_match_types = {
  877. glob = true,
  878. radix = true,
  879. regexp = true,
  880. }
  881. local function add_rbl(key, rbl, global_opts)
  882. if not rbl.symbol then
  883. rbl.symbol = key:upper()
  884. end
  885. local flags_tbl = { 'no_squeeze' }
  886. if rbl.is_whitelist then
  887. flags_tbl[#flags_tbl + 1] = 'nice'
  888. end
  889. -- Check if rbl is available for empty tasks
  890. if not (rbl.emails or rbl.urls or rbl.dkim or rbl.received or rbl.selector or rbl.replyto) or
  891. rbl.is_empty then
  892. flags_tbl[#flags_tbl + 1] = 'empty'
  893. end
  894. if rbl.selector then
  895. rbl.selectors = {}
  896. if type(rbl.selector) ~= 'table' then
  897. rbl.selector = { ['selector'] = rbl.selector }
  898. end
  899. for selector_label, selector in pairs(rbl.selector) do
  900. if known_selectors[selector] then
  901. lua_util.debugm(N, rspamd_config, 'reuse selector id %s',
  902. known_selectors[selector].id)
  903. rbl.selectors[selector_label] = known_selectors[selector].selector
  904. else
  905. if type(rbl.selector_flatten) ~= 'boolean' then
  906. -- Fail-safety
  907. rbl.selector_flatten = true
  908. end
  909. local sel = selectors.create_selector_closure(rspamd_config, selector, '',
  910. rbl.selector_flatten)
  911. if not sel then
  912. rspamd_logger.errx('invalid selector for rbl rule %s: %s', key, selector)
  913. return false
  914. end
  915. rbl.selector = sel
  916. known_selectors[selector] = {
  917. selector = sel,
  918. id = #lua_util.keys(known_selectors) + 1,
  919. }
  920. rbl.selectors[selector_label] = known_selectors[selector].selector
  921. end
  922. end
  923. end
  924. if rbl.process_script then
  925. local ret, f = lua_util.callback_from_string(rbl.process_script)
  926. if ret then
  927. rbl.process_script = f
  928. else
  929. rspamd_logger.errx(rspamd_config,
  930. 'invalid process script for rbl rule %s: %s; %s',
  931. key, rbl.process_script, f)
  932. return false
  933. end
  934. end
  935. if rbl.whitelist then
  936. local def_type = 'set'
  937. if rbl.from or rbl.received then
  938. def_type = 'radix'
  939. end
  940. rbl.whitelist = lua_maps.map_add_from_ucl(rbl.whitelist, def_type,
  941. 'RBL whitelist for ' .. rbl.symbol)
  942. rspamd_logger.infox(rspamd_config, 'added %s whitelist for RBL %s',
  943. def_type, rbl.symbol)
  944. end
  945. local match_type = rbl.returncodes_matcher
  946. if match_type and rbl.returncodes and map_match_types[match_type] then
  947. if not rbl.returncodes_maps then
  948. rbl.returncodes_maps = {}
  949. end
  950. for label, v in pairs(rbl.returncodes) do
  951. if type(v) ~= 'table' then
  952. v = {v}
  953. end
  954. rbl.returncodes_maps[label] = lua_maps.map_add_from_ucl(v, match_type, string.format('%s_%s RBL returncodes', label, rbl.symbol))
  955. end
  956. end
  957. if rbl.url_compose_map then
  958. local lua_urls_compose = require "lua_urls_compose"
  959. rbl.url_compose_map = lua_urls_compose.add_composition_map(rspamd_config, rbl.url_compose_map)
  960. if rbl.url_compose_map then
  961. rspamd_logger.infox(rspamd_config, 'added url composition map for RBL %s',
  962. rbl.symbol)
  963. end
  964. end
  965. if not rbl.whitelist and not rbl.ignore_url_whitelist and (global_opts.url_whitelist or rbl.url_whitelist) and
  966. (rbl.urls or rbl.emails or rbl.dkim or rbl.replyto) and
  967. not (rbl.from or rbl.received) then
  968. local def_type = 'set'
  969. rbl.whitelist = lua_maps.map_add_from_ucl(rbl.url_whitelist or global_opts.url_whitelist, def_type,
  970. 'RBL url whitelist for ' .. rbl.symbol)
  971. rspamd_logger.infox(rspamd_config, 'added URL whitelist for RBL %s',
  972. rbl.symbol)
  973. end
  974. local callback, description = gen_rbl_callback(rbl)
  975. if callback then
  976. local id
  977. if rbl.symbols_prefixes then
  978. id = rspamd_config:register_symbol {
  979. type = 'callback',
  980. callback = callback,
  981. groups = { 'rbl' },
  982. name = rbl.symbol .. '_CHECK',
  983. flags = table.concat(flags_tbl, ',')
  984. }
  985. for _, prefix in pairs(rbl.symbols_prefixes) do
  986. -- For unknown results...
  987. rspamd_config:register_symbol {
  988. type = 'virtual',
  989. parent = id,
  990. group = 'rbl',
  991. score = 0,
  992. name = prefix .. '_' .. rbl.symbol,
  993. }
  994. end
  995. if not (rbl.is_whitelist or rbl.ignore_whitelist) then
  996. table.insert(black_symbols, rbl.symbol .. '_CHECK')
  997. else
  998. lua_util.debugm(N, rspamd_config, 'rule %s ignores whitelists: rbl.is_whitelist = %s, ' ..
  999. 'rbl.ignore_whitelist = %s',
  1000. rbl.symbol, rbl.is_whitelist, rbl.ignore_whitelist)
  1001. end
  1002. else
  1003. id = rspamd_config:register_symbol {
  1004. type = 'callback',
  1005. callback = callback,
  1006. name = rbl.symbol,
  1007. groups = { 'rbl' },
  1008. group = 'rbl',
  1009. score = 0,
  1010. flags = table.concat(flags_tbl, ',')
  1011. }
  1012. if not (rbl.is_whitelist or rbl.ignore_whitelist) then
  1013. table.insert(black_symbols, rbl.symbol)
  1014. else
  1015. lua_util.debugm(N, rspamd_config, 'rule %s ignores whitelists: rbl.is_whitelist = %s, ' ..
  1016. 'rbl.ignore_whitelist = %s',
  1017. rbl.symbol, rbl.is_whitelist, rbl.ignore_whitelist)
  1018. end
  1019. end
  1020. rspamd_logger.infox(rspamd_config, 'added rbl rule %s: %s',
  1021. rbl.symbol, description)
  1022. lua_util.debugm(N, rspamd_config, 'rule dump for %s: %s',
  1023. rbl.symbol, rbl)
  1024. local check_sym = rbl.symbols_prefixes and rbl.symbol .. '_CHECK' or rbl.symbol
  1025. if rbl.dkim then
  1026. rspamd_config:register_dependency(check_sym, 'DKIM_CHECK')
  1027. end
  1028. if rbl.require_symbols then
  1029. for _, dep in ipairs(rbl.require_symbols) do
  1030. rspamd_config:register_dependency(check_sym, dep)
  1031. end
  1032. end
  1033. -- Failure symbol
  1034. rspamd_config:register_symbol {
  1035. type = 'virtual',
  1036. flags = 'nostat',
  1037. name = rbl.symbol .. '_FAIL',
  1038. parent = id,
  1039. score = 0.0,
  1040. }
  1041. local function process_return_code(suffix)
  1042. local function process_specific_suffix(s)
  1043. if s ~= rbl.symbol then
  1044. -- hack
  1045. rspamd_config:register_symbol {
  1046. type = 'virtual',
  1047. parent = id,
  1048. name = s,
  1049. group = 'rbl',
  1050. score = 0,
  1051. }
  1052. end
  1053. if rbl.is_whitelist then
  1054. if rbl.whitelist_exception then
  1055. local found_exception = false
  1056. for _, e in ipairs(rbl.whitelist_exception) do
  1057. if e == s then
  1058. found_exception = true
  1059. break
  1060. end
  1061. end
  1062. if not found_exception then
  1063. table.insert(white_symbols, s)
  1064. end
  1065. else
  1066. table.insert(white_symbols, s)
  1067. end
  1068. else
  1069. if not rbl.ignore_whitelist then
  1070. table.insert(black_symbols, s)
  1071. end
  1072. end
  1073. end
  1074. if rbl.symbols_prefixes then
  1075. for _, prefix in pairs(rbl.symbols_prefixes) do
  1076. process_specific_suffix(prefix .. '_' .. suffix)
  1077. end
  1078. else
  1079. process_specific_suffix(suffix)
  1080. end
  1081. end
  1082. if rbl.returncodes then
  1083. for s, _ in pairs(rbl.returncodes) do
  1084. process_return_code(s)
  1085. end
  1086. end
  1087. if rbl.returnbits then
  1088. for s, _ in pairs(rbl.returnbits) do
  1089. process_return_code(s)
  1090. end
  1091. end
  1092. -- Process monitored
  1093. if not rbl.disable_monitoring then
  1094. if not monitored_addresses[rbl.rbl] then
  1095. monitored_addresses[rbl.rbl] = true
  1096. rbl.monitored = rspamd_config:register_monitored(rbl.rbl, 'dns',
  1097. get_monitored(rbl))
  1098. end
  1099. end
  1100. return true
  1101. end
  1102. return false
  1103. end
  1104. -- Configuration
  1105. local opts = rspamd_config:get_all_opt(N)
  1106. if not (opts and type(opts) == 'table') then
  1107. rspamd_logger.infox(rspamd_config, 'Module is unconfigured')
  1108. lua_util.disable_module(N, "config")
  1109. return
  1110. end
  1111. -- Plugin defaults should not be changed - override these in config
  1112. -- New defaults should not alter behaviour
  1113. opts = lua_util.override_defaults(rbl_common.default_options, opts)
  1114. if opts.rules and opts.rbls then
  1115. -- Common issue :(
  1116. rspamd_logger.infox(rspamd_config, 'merging `rules` and `rbls` keys for compatibility')
  1117. opts.rbls = lua_util.override_defaults(opts.rbls, opts.rules)
  1118. end
  1119. if (opts['local_exclude_ip_map'] ~= nil) then
  1120. local_exclusions = lua_maps.map_add(N, 'local_exclude_ip_map', 'radix',
  1121. 'RBL exclusions map')
  1122. end
  1123. -- TODO: this code should be universal for all modules that use selectors to allow
  1124. -- maps usage from selectors registered for a specific module
  1125. if type(opts.attached_maps) == 'table' then
  1126. opts.attached_maps_processed = {}
  1127. for i, map in ipairs(opts.attached_maps) do
  1128. -- Store maps in the configuration table to keep lifetime track
  1129. opts.attached_maps_processed[i] = lua_maps.map_add_from_ucl(map)
  1130. if opts.attached_maps_processed[i] == nil then
  1131. rspamd_logger.warnx(rspamd_config, "cannot parse attached map: %s", map)
  1132. end
  1133. end
  1134. end
  1135. for key, rbl in pairs(opts.rbls) do
  1136. if type(rbl) ~= 'table' or rbl.disabled == true or rbl.enabled == false then
  1137. rspamd_logger.infox(rspamd_config, 'disable rbl "%s"', key)
  1138. else
  1139. -- Aliases
  1140. if type(rbl.ignore_default) == 'boolean' then
  1141. rbl.ignore_defaults = rbl.ignore_default
  1142. end
  1143. if type(rbl.ignore_whitelists) == 'boolean' then
  1144. rbl.ignore_whitelist = rbl.ignore_whitelists
  1145. end
  1146. -- Propagate default options from opts to rule
  1147. if not rbl.ignore_defaults then
  1148. for default_opt_key, _ in pairs(rbl_common.default_options) do
  1149. local rbl_opt = default_opt_key:sub(#('default_') + 1)
  1150. if rbl[rbl_opt] == nil then
  1151. rbl[rbl_opt] = opts[default_opt_key]
  1152. end
  1153. end
  1154. end
  1155. if not rbl.requests_limit then
  1156. rbl.requests_limit = rspamd_config:get_dns_max_requests()
  1157. end
  1158. local res, err = rbl_common.rule_schema:transform(rbl)
  1159. if not res then
  1160. rspamd_logger.errx(rspamd_config, 'invalid config for %s: %s, RBL is DISABLED',
  1161. key, err)
  1162. else
  1163. res = rbl_common.convert_checks(res, rbl.symbol or key:upper())
  1164. -- Aliases
  1165. if res.return_codes then
  1166. res.returncodes = res.return_codes
  1167. end
  1168. if res.return_bits then
  1169. res.returnbits = res.return_bits
  1170. end
  1171. if not res then
  1172. rspamd_logger.errx(rspamd_config, 'invalid config for %s: %s, RBL is DISABLED',
  1173. key, err)
  1174. else
  1175. add_rbl(key, res, opts)
  1176. end
  1177. end
  1178. end -- rbl.enabled
  1179. end
  1180. -- We now create two symbols:
  1181. -- * RBL_CALLBACK_WHITE that depends on all symbols white
  1182. -- * RBL_CALLBACK that depends on all symbols black to participate in depends chains
  1183. local function rbl_callback_white(task)
  1184. local whitelisted_elements = {}
  1185. for _, w in ipairs(white_symbols) do
  1186. local ws = task:get_symbol(w)
  1187. if ws and ws[1] then
  1188. ws = ws[1]
  1189. if not ws.options then
  1190. ws.options = {}
  1191. end
  1192. for _, opt in ipairs(ws.options) do
  1193. local elt, what = opt:match('^([^:]+):([^:]+)')
  1194. lua_util.debugm(N, task, 'found whitelist from %s: %s(%s)', w,
  1195. elt, what)
  1196. if elt and what then
  1197. whitelisted_elements[elt] = {
  1198. type = what,
  1199. symbol = w,
  1200. }
  1201. end
  1202. end
  1203. end
  1204. end
  1205. task:cache_set('rbl_whitelisted', whitelisted_elements)
  1206. lua_util.debugm(N, task, "finished rbl whitelists processing")
  1207. end
  1208. local function rbl_callback_fin(task)
  1209. -- Do nothing
  1210. lua_util.debugm(N, task, "finished rbl processing")
  1211. end
  1212. rspamd_config:register_symbol {
  1213. type = 'callback',
  1214. callback = rbl_callback_white,
  1215. name = 'RBL_CALLBACK_WHITE',
  1216. flags = 'nice,empty,no_squeeze',
  1217. groups = { 'rbl' },
  1218. augmentations = { string.format("timeout=%f", rspamd_config:get_dns_timeout() or 0.0) },
  1219. }
  1220. rspamd_config:register_symbol {
  1221. type = 'callback',
  1222. callback = rbl_callback_fin,
  1223. name = 'RBL_CALLBACK',
  1224. flags = 'empty,no_squeeze',
  1225. groups = { 'rbl' },
  1226. augmentations = { string.format("timeout=%f", rspamd_config:get_dns_timeout() or 0.0) },
  1227. }
  1228. for _, w in ipairs(white_symbols) do
  1229. rspamd_config:register_dependency('RBL_CALLBACK_WHITE', w)
  1230. end
  1231. for _, b in ipairs(black_symbols) do
  1232. rspamd_config:register_dependency(b, 'RBL_CALLBACK_WHITE')
  1233. rspamd_config:register_dependency('RBL_CALLBACK', b)
  1234. end