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

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