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

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