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.

multimap.lua 36KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. if confighelp then
  14. return
  15. end
  16. -- Multimap is rspamd module designed to define and operate with different maps
  17. local rules = {}
  18. local rspamd_logger = require "rspamd_logger"
  19. local rspamd_util = require "rspamd_util"
  20. local rspamd_regexp = require "rspamd_regexp"
  21. local rspamd_expression = require "rspamd_expression"
  22. local rspamd_ip = require "rspamd_ip"
  23. local lua_util = require "lua_util"
  24. local lua_selectors = require "lua_selectors"
  25. local lua_maps = require "lua_maps"
  26. local redis_params
  27. local fun = require "fun"
  28. local N = 'multimap'
  29. local value_types = {
  30. ip = {
  31. get_value = function(ip) return ip:to_string() end,
  32. },
  33. from = {
  34. get_value = function(val) return val end,
  35. },
  36. helo = {
  37. get_value = function(val) return val end,
  38. },
  39. header = {
  40. get_value = function(val) return val end,
  41. },
  42. rcpt = {
  43. get_value = function(val) return val end,
  44. },
  45. user = {
  46. get_value = function(val) return val end,
  47. },
  48. url = {
  49. get_value = function(val) return val end,
  50. },
  51. dnsbl = {
  52. get_value = function(ip) return ip:to_string() end,
  53. },
  54. filename = {
  55. get_value = function(val) return val end,
  56. },
  57. content = {
  58. get_value = function() return nil end,
  59. },
  60. hostname = {
  61. get_value = function(val) return val end,
  62. },
  63. asn = {
  64. get_value = function(val) return val end,
  65. },
  66. country = {
  67. get_value = function(val) return val end,
  68. },
  69. received = {
  70. get_value = function(val) return val end,
  71. },
  72. mempool = {
  73. get_value = function(val) return val end,
  74. },
  75. selector = {
  76. get_value = function(val) return val end,
  77. },
  78. symbol_options = {
  79. get_value = function(val) return val end,
  80. },
  81. }
  82. local function ip_to_rbl(ip, rbl)
  83. return table.concat(ip:inversed_str_octets(), ".") .. '.' .. rbl
  84. end
  85. local function apply_hostname_filter(task, filter, hostname, r)
  86. if filter == 'tld' then
  87. local tld = rspamd_util.get_tld(hostname)
  88. return tld
  89. elseif filter == 'top' then
  90. local tld = rspamd_util.get_tld(hostname)
  91. return tld:match('[^.]*$') or tld
  92. else
  93. if not r['re_filter'] then
  94. local pat = string.match(filter, 'tld:regexp:(.+)')
  95. if not pat then
  96. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  97. return
  98. end
  99. r['re_filter'] = rspamd_regexp.create_cached(pat)
  100. if not r['re_filter'] then
  101. rspamd_logger.errx(task, 'couldnt create regex: %s', pat)
  102. return
  103. end
  104. end
  105. local tld = rspamd_util.get_tld(hostname)
  106. local res = r['re_filter']:search(tld)
  107. if res then
  108. return res[1]
  109. else
  110. return nil
  111. end
  112. end
  113. end
  114. local function apply_url_filter(task, filter, url, r)
  115. if not filter then
  116. return url:get_host()
  117. end
  118. if filter == 'tld' then
  119. return url:get_tld()
  120. elseif filter == 'top' then
  121. local tld = url:get_tld()
  122. return tld:match('[^.]*$') or tld
  123. elseif filter == 'full' then
  124. return url:get_text()
  125. elseif filter == 'is_phished' then
  126. if url:is_phished() then
  127. return url:get_host()
  128. else
  129. return nil
  130. end
  131. elseif filter == 'is_redirected' then
  132. if url:is_redirected() then
  133. return url:get_host()
  134. else
  135. return nil
  136. end
  137. elseif filter == 'is_obscured' then
  138. if url:is_obscured() then
  139. return url:get_host()
  140. else
  141. return nil
  142. end
  143. elseif filter == 'path' then
  144. return url:get_path()
  145. elseif filter == 'query' then
  146. return url:get_query()
  147. elseif string.find(filter, 'tag:') then
  148. local tags = url:get_tags()
  149. local want_tag = string.match(filter, 'tag:(.*)')
  150. for _, t in ipairs(tags) do
  151. if t == want_tag then
  152. return url:get_host()
  153. end
  154. end
  155. return nil
  156. elseif string.find(filter, 'tld:regexp:') then
  157. if not r['re_filter'] then
  158. local type,pat = string.match(filter, '(regexp:)(.+)')
  159. if type and pat then
  160. r['re_filter'] = rspamd_regexp.create_cached(pat)
  161. end
  162. end
  163. if not r['re_filter'] then
  164. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  165. else
  166. local results = r['re_filter']:search(url:get_tld())
  167. if results then
  168. return results[1]
  169. else
  170. return nil
  171. end
  172. end
  173. elseif string.find(filter, 'full:regexp:') then
  174. if not r['re_filter'] then
  175. local type,pat = string.match(filter, '(regexp:)(.+)')
  176. if type and pat then
  177. r['re_filter'] = rspamd_regexp.create_cached(pat)
  178. end
  179. end
  180. if not r['re_filter'] then
  181. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  182. else
  183. local results = r['re_filter']:search(url:get_text())
  184. if results then
  185. return results[1]
  186. else
  187. return nil
  188. end
  189. end
  190. elseif string.find(filter, 'regexp:') then
  191. if not r['re_filter'] then
  192. local type,pat = string.match(filter, '(regexp:)(.+)')
  193. if type and pat then
  194. r['re_filter'] = rspamd_regexp.create_cached(pat)
  195. end
  196. end
  197. if not r['re_filter'] then
  198. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  199. else
  200. local results = r['re_filter']:search(url:get_host())
  201. if results then
  202. return results[1]
  203. else
  204. return nil
  205. end
  206. end
  207. elseif string.find(filter, '^template:') then
  208. if not r['template'] then
  209. r['template'] = string.match(filter, '^template:(.+)')
  210. end
  211. if r['template'] then
  212. return lua_util.template(r['template'], url:to_table())
  213. end
  214. end
  215. return url:get_host()
  216. end
  217. local function apply_addr_filter(task, filter, input, rule)
  218. if filter == 'email:addr' or filter == 'email' then
  219. local addr = rspamd_util.parse_mail_address(input, task:get_mempool(), 1024)
  220. if addr and addr[1] then
  221. return fun.totable(fun.map(function(a) return a.addr end, addr))
  222. end
  223. elseif filter == 'email:user' then
  224. local addr = rspamd_util.parse_mail_address(input, task:get_mempool(), 1024)
  225. if addr and addr[1] then
  226. return fun.totable(fun.map(function(a) return a.user end, addr))
  227. end
  228. elseif filter == 'email:domain' then
  229. local addr = rspamd_util.parse_mail_address(input, task:get_mempool(), 1024)
  230. if addr and addr[1] then
  231. return fun.totable(fun.map(function(a) return a.domain end, addr))
  232. end
  233. elseif filter == 'email:domain:tld' then
  234. local addr = rspamd_util.parse_mail_address(input, task:get_mempool(), 1024)
  235. if addr and addr[1] then
  236. return fun.totable(fun.map(function(a) return rspamd_util.get_tld(a.domain) end, addr))
  237. end
  238. elseif filter == 'email:name' then
  239. local addr = rspamd_util.parse_mail_address(input, task:get_mempool(), 1024)
  240. if addr and addr[1] then
  241. return fun.totable(fun.map(function(a) return a.name end, addr))
  242. end
  243. elseif filter == 'ip_addr' then
  244. local ip_addr = rspamd_ip.from_string(input)
  245. if ip_addr and ip_addr:is_valid() then
  246. return ip_addr
  247. end
  248. else
  249. -- regexp case
  250. if not rule['re_filter'] then
  251. local type,pat = string.match(filter, '(regexp:)(.+)')
  252. if type and pat then
  253. rule['re_filter'] = rspamd_regexp.create_cached(pat)
  254. end
  255. end
  256. if not rule['re_filter'] then
  257. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  258. else
  259. local results = rule['re_filter']:search(input)
  260. if results then
  261. return results[1]
  262. end
  263. end
  264. end
  265. return input
  266. end
  267. local function apply_filename_filter(task, filter, fn, r)
  268. if filter == 'extension' or filter == 'ext' then
  269. return string.match(fn, '%.([^.]+)$')
  270. elseif string.find(filter, 'regexp:') then
  271. if not r['re_filter'] then
  272. local type,pat = string.match(filter, '(regexp:)(.+)')
  273. if type and pat then
  274. r['re_filter'] = rspamd_regexp.create_cached(pat)
  275. end
  276. end
  277. if not r['re_filter'] then
  278. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  279. else
  280. local results = r['re_filter']:search(fn)
  281. if results then
  282. return results[1]
  283. else
  284. return nil
  285. end
  286. end
  287. end
  288. return fn
  289. end
  290. local function apply_regexp_filter(task, filter, fn, r)
  291. if string.find(filter, 'regexp:') then
  292. if not r['re_filter'] then
  293. local type,pat = string.match(filter, '(regexp:)(.+)')
  294. if type and pat then
  295. r['re_filter'] = rspamd_regexp.create_cached(pat)
  296. end
  297. end
  298. if not r['re_filter'] then
  299. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  300. else
  301. local results = r['re_filter']:search(fn, false, true)
  302. if results then
  303. return results[1][2]
  304. else
  305. return nil
  306. end
  307. end
  308. end
  309. return fn
  310. end
  311. local function apply_content_filter(task, filter)
  312. if filter == 'body' then
  313. return {task:get_rawbody()}
  314. elseif filter == 'full' then
  315. return {task:get_content()}
  316. elseif filter == 'headers' then
  317. return {task:get_raw_headers()}
  318. elseif filter == 'text' then
  319. local ret = {}
  320. for _,p in ipairs(task:get_text_parts()) do
  321. table.insert(ret, p:get_content())
  322. end
  323. return ret
  324. elseif filter == 'rawtext' then
  325. local ret = {}
  326. for _,p in ipairs(task:get_text_parts()) do
  327. table.insert(ret, p:get_raw_content())
  328. end
  329. return ret
  330. elseif filter == 'oneline' then
  331. local ret = {}
  332. for _,p in ipairs(task:get_text_parts()) do
  333. table.insert(ret, p:get_content_oneline())
  334. end
  335. return ret
  336. else
  337. rspamd_logger.errx(task, 'bad search filter: %s', filter)
  338. end
  339. return {}
  340. end
  341. local multimap_filters = {
  342. from = apply_addr_filter,
  343. rcpt = apply_addr_filter,
  344. helo = apply_hostname_filter,
  345. symbol_options = apply_regexp_filter,
  346. header = apply_addr_filter,
  347. url = apply_url_filter,
  348. filename = apply_filename_filter,
  349. mempool = apply_regexp_filter,
  350. selector = apply_regexp_filter,
  351. hostname = apply_hostname_filter,
  352. --content = apply_content_filter, -- Content filters are special :(
  353. }
  354. local multimap_grammar
  355. local function multimap_query_redis(key, task, value, callback)
  356. local cmd = 'HGET'
  357. if type(value) == 'userdata' and value.class == 'rspamd{ip}' then
  358. cmd = 'HMGET'
  359. end
  360. local srch = {key}
  361. -- Insert all ips for some mask :(
  362. if type(value) == 'userdata' and value.class == 'rspamd{ip}' then
  363. srch[#srch + 1] = tostring(value)
  364. -- IPv6 case
  365. local maxbits = 128
  366. local minbits = 64
  367. if value:get_version() == 4 then
  368. maxbits = 32
  369. minbits = 8
  370. end
  371. for i=maxbits,minbits,-1 do
  372. local nip = value:apply_mask(i):tostring() .. "/" .. i
  373. srch[#srch + 1] = nip
  374. end
  375. else
  376. srch[#srch + 1] = value
  377. end
  378. local function redis_map_cb(err, data)
  379. lua_util.debugm(N, task, 'got reply from Redis when trying to get key %s: err=%s, data=%s',
  380. key, err, data)
  381. if not err and type(data) ~= 'userdata' then
  382. callback(data)
  383. end
  384. end
  385. return rspamd_redis_make_request(task,
  386. redis_params, -- connect params
  387. key, -- hash key
  388. false, -- is write
  389. redis_map_cb, --callback
  390. cmd, -- command
  391. srch -- arguments
  392. )
  393. end
  394. local function multimap_callback(task, rule)
  395. local pre_filter = rule['prefilter']
  396. local function match_element(r, value, callback)
  397. if not value then
  398. return false
  399. end
  400. lua_util.debugm(N, task, 'check value %s for multimap %s', value,
  401. rule.symbol)
  402. local ret = false
  403. if r.redis_key then
  404. -- Deal with hash name here: it can be either plain string or a selector
  405. if type(r.redis_key) == 'string' then
  406. ret = multimap_query_redis(r.redis_key, task, value, callback)
  407. else
  408. -- Here we have a selector
  409. local results = r.redis_key(task)
  410. -- Here we need to spill this function into multiple queries
  411. if type(results) == 'table' then
  412. for _,res in ipairs(results) do
  413. ret = multimap_query_redis(res, task, value, callback)
  414. if not ret then
  415. break
  416. end
  417. end
  418. else
  419. ret = multimap_query_redis(results, task, value, callback)
  420. end
  421. end
  422. return ret
  423. elseif r.radix then
  424. ret = r.radix:get_key(value)
  425. elseif r.hash then
  426. if type(value) == 'userdata' then
  427. if value.class == 'rspamd{ip}' then
  428. value = value:tostring()
  429. end
  430. end
  431. ret = r.hash:get_key(value)
  432. end
  433. lua_util.debugm(N, task, 'found return "%s" for multimap %s', ret,
  434. rule.symbol)
  435. if ret then
  436. if type(ret) == 'table' then
  437. for _,elt in ipairs(ret) do
  438. callback(elt)
  439. end
  440. ret = true
  441. else
  442. callback(ret)
  443. end
  444. end
  445. return ret
  446. end
  447. -- Parse result in form: <symbol>:<score>|<symbol>|<score>
  448. local function parse_ret(parse_rule, p_ret)
  449. if p_ret and type(p_ret) == 'string' then
  450. local lpeg = require "lpeg"
  451. if not multimap_grammar then
  452. local number = {}
  453. local digit = lpeg.R("09")
  454. number.integer =
  455. (lpeg.S("+-") ^ -1) *
  456. (digit ^ 1)
  457. -- Matches: .6, .899, .9999873
  458. number.fractional =
  459. (lpeg.P(".") ) *
  460. (digit ^ 1)
  461. -- Matches: 55.97, -90.8, .9
  462. number.decimal =
  463. (number.integer * -- Integer
  464. (number.fractional ^ -1)) + -- Fractional
  465. (lpeg.S("+-") * number.fractional) -- Completely fractional number
  466. local sym_start = lpeg.R("az", "AZ") + lpeg.S("_")
  467. local sym_elt = sym_start + lpeg.R("09")
  468. local symbol = sym_start * sym_elt ^0
  469. local symbol_cap = lpeg.Cg(symbol, 'symbol')
  470. local score_cap = lpeg.Cg(number.decimal, 'score')
  471. local symscore_cap = (symbol_cap * lpeg.P(":") * score_cap)
  472. local grammar = symscore_cap + symbol_cap + score_cap
  473. multimap_grammar = lpeg.Ct(grammar)
  474. end
  475. local tbl = multimap_grammar:match(p_ret)
  476. if tbl then
  477. local sym
  478. local score = 1.0
  479. if tbl['symbol'] then
  480. sym = tbl['symbol']
  481. end
  482. if tbl['score'] then
  483. score = tbl['score']
  484. end
  485. return true,sym,score
  486. else
  487. if p_ret ~= '' then
  488. rspamd_logger.infox(task, '%s: cannot parse string "%s"',
  489. parse_rule.symbol, p_ret)
  490. end
  491. return true,nil,1.0
  492. end
  493. elseif type(p_ret) == 'boolean' then
  494. return p_ret,nil,1.0
  495. end
  496. return false,nil,0.0
  497. end
  498. local function insert_results(result, opt)
  499. local _,symbol,score = parse_ret(rule, result)
  500. local forced = false
  501. if symbol then
  502. if rule.symbols_set then
  503. if not rule.symbols_set[symbol] then
  504. rspamd_logger.infox(task, 'symbol %s is not registered for map %s, ' ..
  505. 'replace it with just %s',
  506. symbol, rule.symbol, rule.symbol)
  507. symbol = rule.symbol
  508. end
  509. elseif rule.disable_multisymbol then
  510. symbol = rule.symbol
  511. if type(opt) == 'table' then
  512. table.insert(opt, result)
  513. elseif type(opt) ~= nil then
  514. opt = {opt,result}
  515. else
  516. opt = {result}
  517. end
  518. else
  519. forced = true
  520. end
  521. else
  522. symbol = rule.symbol
  523. end
  524. if opt then
  525. if type(opt) == 'table' then
  526. task:insert_result(forced, symbol, score, fun.totable(fun.map(tostring, opt)))
  527. else
  528. task:insert_result(forced, symbol, score, tostring(opt))
  529. end
  530. else
  531. task:insert_result(forced, symbol, score)
  532. end
  533. if pre_filter then
  534. local message = rule.message
  535. if rule.message_func then
  536. message = rule.message_func(task, rule.symbol, opt)
  537. end
  538. if message then
  539. task:set_pre_result(rule.action, message, N)
  540. else
  541. task:set_pre_result(rule.action, 'Matched map: ' .. rule.symbol, N)
  542. end
  543. end
  544. end
  545. -- Match a single value for against a single rule
  546. local function match_rule(r, value)
  547. local function rule_callback(result)
  548. if result then
  549. if type(result) == 'table' then
  550. for _,rs in ipairs(result) do
  551. if type(rs) ~= 'userdata' then
  552. rule_callback(rs)
  553. end
  554. end
  555. return
  556. end
  557. local opt = value_types[r['type']].get_value(value)
  558. insert_results(result, opt)
  559. end
  560. end
  561. if r.filter or r.type == 'url' then
  562. local fn = multimap_filters[r.type]
  563. if fn then
  564. local filtered_value = fn(task, r.filter, value, r)
  565. lua_util.debugm(N, task, 'apply filter %s for rule %s: %s -> %s',
  566. r.filter, r.symbol, value, filtered_value)
  567. value = filtered_value
  568. end
  569. end
  570. if type(value) == 'table' then
  571. fun.each(function(elt) match_element(r, elt, rule_callback) end, value)
  572. else
  573. match_element(r, value, rule_callback)
  574. end
  575. end
  576. -- Match list of values according to the field
  577. local function match_list(r, ls, fields)
  578. if ls then
  579. if fields then
  580. fun.each(function(e)
  581. local match = e[fields[1]]
  582. if match then
  583. if fields[2] then
  584. match = fields[2](match)
  585. end
  586. match_rule(r, match)
  587. end
  588. end, ls)
  589. else
  590. fun.each(function(e) match_rule(r, e) end, ls)
  591. end
  592. end
  593. end
  594. local function match_addr(r, addr)
  595. match_list(r, addr, {'addr'})
  596. if not r.filter then
  597. match_list(r, addr, {'domain'})
  598. match_list(r, addr, {'user'})
  599. end
  600. end
  601. local function match_url(r, url)
  602. match_rule(r, url)
  603. end
  604. local function match_hostname(r, hostname)
  605. match_rule(r, hostname)
  606. end
  607. local function match_filename(r, fn)
  608. match_rule(r, fn)
  609. end
  610. local function match_received_header(r, pos, total, h)
  611. local use_tld = false
  612. local filter = r['filter'] or 'real_ip'
  613. if filter:match('^tld:') then
  614. filter = filter:sub(5)
  615. use_tld = true
  616. end
  617. local v = h[filter]
  618. if v then
  619. local min_pos = tonumber(r['min_pos'])
  620. local max_pos = tonumber(r['max_pos'])
  621. if min_pos then
  622. if min_pos < 0 then
  623. if min_pos == -1 then
  624. if (pos ~= total) then
  625. return
  626. end
  627. else
  628. if pos <= (total - (min_pos*-1)) then
  629. return
  630. end
  631. end
  632. elseif pos < min_pos then
  633. return
  634. end
  635. end
  636. if max_pos then
  637. if max_pos < -1 then
  638. if (total - (max_pos*-1)) >= pos then
  639. return
  640. end
  641. elseif max_pos > 0 then
  642. if pos > max_pos then
  643. return
  644. end
  645. end
  646. end
  647. local match_flags = r['flags']
  648. local nmatch_flags = r['nflags']
  649. if match_flags or nmatch_flags then
  650. local got_flags = h['flags']
  651. if match_flags then
  652. for _, flag in ipairs(match_flags) do
  653. if not got_flags[flag] then return end
  654. end
  655. end
  656. if nmatch_flags then
  657. for _, flag in ipairs(nmatch_flags) do
  658. if got_flags[flag] then return end
  659. end
  660. end
  661. end
  662. if filter == 'real_ip' or filter == 'from_ip' then
  663. if type(v) == 'string' then
  664. v = rspamd_ip.from_string(v)
  665. end
  666. if v and v:is_valid() then
  667. match_rule(r, v)
  668. end
  669. else
  670. if use_tld and type(v) == 'string' then
  671. v = rspamd_util.get_tld(v)
  672. end
  673. match_rule(r, v)
  674. end
  675. end
  676. end
  677. local function match_content(r)
  678. local data
  679. if r['filter'] then
  680. data = apply_content_filter(task, r['filter'], r)
  681. else
  682. data = {task:get_content()}
  683. end
  684. for _,v in ipairs(data) do
  685. match_rule(r, v)
  686. end
  687. end
  688. if rule.expression and not rule.combined then
  689. local res,trace = rule['expression']:process_traced(task)
  690. if not res or res == 0 then
  691. lua_util.debugm(N, task, 'condition is false for %s',
  692. rule.symbol)
  693. return
  694. else
  695. lua_util.debugm(N, task, 'condition is true for %s: %s',
  696. rule.symbol,
  697. trace)
  698. end
  699. end
  700. local process_rule_funcs = {
  701. ip = function()
  702. local ip = task:get_from_ip()
  703. if ip and ip:is_valid() then
  704. match_rule(rule, ip)
  705. end
  706. end,
  707. dnsbl = function()
  708. local ip = task:get_from_ip()
  709. if ip and ip:is_valid() then
  710. local to_resolve = ip_to_rbl(ip, rule['map'])
  711. local function dns_cb(_, _, results, err)
  712. lua_util.debugm(N, rspamd_config,
  713. 'resolve() finished: results=%1, err=%2, to_resolve=%3',
  714. results, err, to_resolve)
  715. if err and
  716. (err ~= 'requested record is not found' and
  717. err ~= 'no records with this name') then
  718. rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, results)
  719. elseif results then
  720. task:insert_result(rule['symbol'], 1, rule['map'])
  721. if pre_filter then
  722. task:set_pre_result(rule['action'],
  723. 'Matched map: ' .. rule['symbol'], N)
  724. end
  725. end
  726. end
  727. task:get_resolver():resolve_a({
  728. task= task,
  729. name = to_resolve,
  730. callback = dns_cb,
  731. forced = true
  732. })
  733. end
  734. end,
  735. header = function()
  736. if type(rule['header']) == 'table' then
  737. for _,rh in ipairs(rule['header']) do
  738. local hv = task:get_header_full(rh)
  739. match_list(rule, hv, {'decoded'})
  740. end
  741. else
  742. local hv = task:get_header_full(rule['header'])
  743. match_list(rule, hv, {'decoded'})
  744. end
  745. end,
  746. rcpt = function()
  747. if task:has_recipients('smtp') then
  748. local rcpts = task:get_recipients('smtp')
  749. match_addr(rule, rcpts)
  750. elseif task:has_recipients('mime') then
  751. local rcpts = task:get_recipients('mime')
  752. match_addr(rule, rcpts)
  753. end
  754. end,
  755. from = function()
  756. if task:has_from('smtp') then
  757. local from = task:get_from('smtp')
  758. match_addr(rule, from)
  759. elseif task:has_from('mime') then
  760. local from = task:get_from('mime')
  761. match_addr(rule, from)
  762. end
  763. end,
  764. helo = function()
  765. local helo = task:get_helo()
  766. if helo then
  767. match_hostname(rule, helo)
  768. end
  769. end,
  770. url = function()
  771. if task:has_urls() then
  772. local msg_urls = task:get_urls()
  773. for _,url in ipairs(msg_urls) do
  774. match_url(rule, url)
  775. end
  776. end
  777. end,
  778. user = function()
  779. local user = task:get_user()
  780. if user then
  781. match_rule(rule, user)
  782. end
  783. end,
  784. filename = function()
  785. local parts = task:get_parts()
  786. local function filter_parts(p)
  787. return p:is_attachment() or (not p:is_text()) and (not p:is_multipart())
  788. end
  789. local function filter_archive(p)
  790. local ext = p:get_detected_ext()
  791. local det_type = 'unknown'
  792. if ext then
  793. local lua_magic_types = require "lua_magic/types"
  794. local det_t = lua_magic_types[ext]
  795. if det_t then
  796. det_type = det_t.type
  797. end
  798. end
  799. return p:is_archive() and det_type == 'archive' and not rule.skip_archives
  800. end
  801. for _,p in fun.iter(fun.filter(filter_parts, parts)) do
  802. if filter_archive(p) then
  803. local fnames = p:get_archive():get_files(1000)
  804. for _,fn in ipairs(fnames) do
  805. match_filename(rule, fn)
  806. end
  807. end
  808. local fn = p:get_filename()
  809. if fn then
  810. match_filename(rule, fn)
  811. end
  812. -- Also deal with detected content type
  813. if not rule.skip_detected then
  814. local ext = p:get_detected_ext()
  815. if ext then
  816. local fake_fname = string.format('detected.%s', ext)
  817. lua_util.debugm(N, task, 'detected filename %s',
  818. fake_fname)
  819. match_filename(rule, fake_fname)
  820. end
  821. end
  822. end
  823. end,
  824. content = function()
  825. match_content(rule)
  826. end,
  827. hostname = function()
  828. local hostname = task:get_hostname()
  829. if hostname then
  830. match_hostname(rule, hostname)
  831. end
  832. end,
  833. asn = function()
  834. local asn = task:get_mempool():get_variable('asn')
  835. if asn then
  836. match_rule(rule, asn)
  837. end
  838. end,
  839. country = function()
  840. local country = task:get_mempool():get_variable('country')
  841. if country then
  842. match_rule(rule, country)
  843. end
  844. end,
  845. mempool = function()
  846. local var = task:get_mempool():get_variable(rule['variable'])
  847. if var then
  848. match_rule(rule, var)
  849. end
  850. end,
  851. symbol_options = function()
  852. local sym = task:get_symbol(rule['target_symbol'])
  853. if sym and sym[1].options then
  854. for _, o in ipairs(sym[1].options) do
  855. match_rule(rule, o)
  856. end
  857. end
  858. end,
  859. received = function()
  860. local hdrs = task:get_received_headers()
  861. if hdrs and hdrs[1] then
  862. if not rule['artificial'] then
  863. hdrs = fun.filter(function(h)
  864. return not h['flags']['artificial']
  865. end, hdrs):totable()
  866. end
  867. for pos, h in ipairs(hdrs) do
  868. match_received_header(rule, pos, #hdrs, h)
  869. end
  870. end
  871. end,
  872. selector = function()
  873. local elts = rule.selector(task)
  874. if elts then
  875. if type(elts) == 'table' then
  876. for _,elt in ipairs(elts) do
  877. match_rule(rule, elt)
  878. end
  879. else
  880. match_rule(rule, elts)
  881. end
  882. end
  883. end,
  884. combined = function()
  885. local ret,trace = rule.combined:process(task)
  886. if ret and ret ~= 0 then
  887. for n,t in pairs(trace) do
  888. insert_results(t.value, string.format("%s=%s",
  889. n, t.matched))
  890. end
  891. end
  892. end,
  893. }
  894. local rt = rule.type
  895. local process_func = process_rule_funcs[rt]
  896. if process_func then
  897. process_func()
  898. else
  899. rspamd_logger.errx(task, 'Unrecognised rule type: %s', rt)
  900. end
  901. end
  902. local function gen_multimap_callback(rule)
  903. return function(task)
  904. multimap_callback(task, rule)
  905. end
  906. end
  907. local function add_multimap_rule(key, newrule)
  908. local ret = false
  909. local function multimap_load_hash(rule)
  910. if rule['regexp'] then
  911. if rule['multi'] then
  912. rule.hash = lua_maps.map_add_from_ucl(rule.map, 'regexp_multi',
  913. rule.description)
  914. else
  915. rule.hash = lua_maps.map_add_from_ucl(rule.map, 'regexp',
  916. rule.description)
  917. end
  918. elseif rule['glob'] then
  919. if rule['multi'] then
  920. rule.hash = lua_maps.map_add_from_ucl(rule.map, 'glob_multi',
  921. rule.description)
  922. else
  923. rule.hash = lua_maps.map_add_from_ucl(rule.map, 'glob',
  924. rule.description)
  925. end
  926. else
  927. rule.hash = lua_maps.map_add_from_ucl(rule.map, 'hash',
  928. rule.description)
  929. end
  930. end
  931. local known_generic_types = {
  932. header = true,
  933. rcpt = true,
  934. from = true,
  935. helo = true,
  936. symbol_options = true,
  937. filename = true,
  938. url = true,
  939. user = true,
  940. content = true,
  941. hostname = true,
  942. asn = true,
  943. country = true,
  944. mempool = true,
  945. selector = true,
  946. combined = true
  947. }
  948. if newrule['message_func'] then
  949. newrule['message_func'] = assert(load(newrule['message_func']))()
  950. end
  951. if newrule['url'] and not newrule['map'] then
  952. newrule['map'] = newrule['url']
  953. end
  954. if not (newrule.map or newrule.rules) then
  955. rspamd_logger.errx(rspamd_config, 'incomplete rule, missing map')
  956. return nil
  957. end
  958. if not newrule['symbol'] and key then
  959. newrule['symbol'] = key
  960. elseif not newrule['symbol'] then
  961. rspamd_logger.errx(rspamd_config, 'incomplete rule, missing symbol')
  962. return nil
  963. end
  964. if not newrule['description'] then
  965. newrule['description'] = string.format('multimap, type %s: %s', newrule['type'],
  966. newrule['symbol'])
  967. end
  968. if newrule['type'] == 'mempool' and not newrule['variable'] then
  969. rspamd_logger.errx(rspamd_config, 'mempool map requires variable')
  970. return nil
  971. end
  972. if newrule['type'] == 'selector' then
  973. if not newrule['selector'] then
  974. rspamd_logger.errx(rspamd_config, 'selector map requires selector definition')
  975. return nil
  976. else
  977. local selector = lua_selectors.create_selector_closure(
  978. rspamd_config, newrule['selector'], newrule['delimiter'] or "")
  979. if not selector then
  980. rspamd_logger.errx(rspamd_config, 'selector map has invalid selector: "%s", symbol: %s',
  981. newrule['selector'], newrule['symbol'])
  982. return nil
  983. end
  984. newrule.selector = selector
  985. end
  986. end
  987. if type(newrule['map']) == 'string' and
  988. string.find(newrule['map'], '^redis://.*$') then
  989. if not redis_params then
  990. rspamd_logger.infox(rspamd_config, 'no redis servers are specified, ' ..
  991. 'cannot add redis map %s: %s', newrule['symbol'], newrule['map'])
  992. return nil
  993. end
  994. newrule['redis_key'] = string.match(newrule['map'], '^redis://(.*)$')
  995. if newrule['redis_key'] then
  996. ret = true
  997. end
  998. elseif type(newrule['map']) == 'string' and
  999. string.find(newrule['map'], '^redis%+selector://.*$') then
  1000. if not redis_params then
  1001. rspamd_logger.infox(rspamd_config, 'no redis servers are specified, ' ..
  1002. 'cannot add redis map %s: %s', newrule['symbol'], newrule['map'])
  1003. return nil
  1004. end
  1005. local selector_str = string.match(newrule['map'], '^redis%+selector://(.*)$')
  1006. local selector = lua_selectors.create_selector_closure(
  1007. rspamd_config, selector_str, newrule['delimiter'] or "")
  1008. if not selector then
  1009. rspamd_logger.errx(rspamd_config, 'redis selector map has invalid selector: "%s", symbol: %s',
  1010. selector_str, newrule['symbol'])
  1011. return nil
  1012. end
  1013. newrule['redis_key'] = selector
  1014. ret = true
  1015. elseif newrule.type == 'combined' then
  1016. local lua_maps_expressions = require "lua_maps_expressions"
  1017. newrule.combined = lua_maps_expressions.create(rspamd_config,
  1018. {
  1019. rules = newrule.rules,
  1020. expression = newrule.expression
  1021. }, N, 'Combined map for ' .. newrule.symbol)
  1022. if not newrule.combined then
  1023. rspamd_logger.errx(rspamd_config, 'cannot add combined map for %s', newrule.symbol)
  1024. else
  1025. ret = true
  1026. end
  1027. else
  1028. if newrule['type'] == 'ip' then
  1029. newrule['radix'] = lua_maps.map_add_from_ucl(newrule.map, 'radix',
  1030. newrule.description)
  1031. if newrule['radix'] then
  1032. ret = true
  1033. else
  1034. rspamd_logger.warnx(rspamd_config, 'Cannot add rule: map doesn\'t exists: %1',
  1035. newrule['map'])
  1036. end
  1037. elseif newrule['type'] == 'received' then
  1038. if type(newrule['flags']) == 'table' and newrule['flags'][1] then
  1039. newrule['flags'] = newrule['flags']
  1040. elseif type(newrule['flags']) == 'string' then
  1041. newrule['flags'] = {newrule['flags']}
  1042. end
  1043. if type(newrule['nflags']) == 'table' and newrule['nflags'][1] then
  1044. newrule['nflags'] = newrule['nflags']
  1045. elseif type(newrule['nflags']) == 'string' then
  1046. newrule['nflags'] = {newrule['nflags']}
  1047. end
  1048. local filter = newrule['filter'] or 'real_ip'
  1049. if filter == 'real_ip' or filter == 'from_ip' then
  1050. newrule['radix'] = lua_maps.map_add_from_ucl(newrule.map, 'radix',
  1051. newrule.description)
  1052. if newrule['radix'] then
  1053. ret = true
  1054. end
  1055. else
  1056. multimap_load_hash(newrule)
  1057. if newrule['hash'] then
  1058. ret = true
  1059. else
  1060. rspamd_logger.warnx(rspamd_config, 'Cannot add rule: map doesn\'t exists: %1',
  1061. newrule['map'])
  1062. end
  1063. end
  1064. elseif known_generic_types[newrule.type] then
  1065. if newrule.filter == 'ip_addr' then
  1066. newrule['radix'] = lua_maps.map_add_from_ucl(newrule.map, 'radix',
  1067. newrule.description)
  1068. elseif not newrule.combined then
  1069. multimap_load_hash(newrule)
  1070. end
  1071. if newrule.hash or newrule.radix then
  1072. ret = true
  1073. else
  1074. rspamd_logger.warnx(rspamd_config, 'Cannot add rule: map doesn\'t exists: %1',
  1075. newrule['map'])
  1076. end
  1077. elseif newrule['type'] == 'dnsbl' then
  1078. ret = true
  1079. end
  1080. end
  1081. if newrule['action'] then
  1082. newrule['prefilter'] = true
  1083. else
  1084. newrule['prefilter'] = false
  1085. end
  1086. if ret then
  1087. if newrule['type'] == 'symbol_options' then
  1088. rspamd_config:register_dependency(newrule['symbol'], newrule['target_symbol'])
  1089. end
  1090. if newrule['require_symbols'] and not newrule['prefilter'] then
  1091. local atoms = {}
  1092. local function parse_atom(str)
  1093. local atom = table.concat(fun.totable(fun.take_while(function(c)
  1094. if string.find(', \t()><+!|&\n', c) then
  1095. return false
  1096. end
  1097. return true
  1098. end, fun.iter(str))), '')
  1099. table.insert(atoms, atom)
  1100. return atom
  1101. end
  1102. local function process_atom(atom, task)
  1103. local f_ret = task:has_symbol(atom)
  1104. lua_util.debugm(N, rspamd_config, 'check for symbol %s: %s', atom, f_ret)
  1105. if f_ret then
  1106. return 1
  1107. end
  1108. return 0
  1109. end
  1110. local expression = rspamd_expression.create(newrule['require_symbols'],
  1111. {parse_atom, process_atom}, rspamd_config:get_mempool())
  1112. if expression then
  1113. newrule['expression'] = expression
  1114. fun.each(function(v)
  1115. lua_util.debugm(N, rspamd_config, 'add dependency %s -> %s',
  1116. newrule['symbol'], v)
  1117. rspamd_config:register_dependency(newrule['symbol'], v)
  1118. end, atoms)
  1119. end
  1120. end
  1121. return newrule
  1122. end
  1123. return nil
  1124. end
  1125. -- Registration
  1126. local opts = rspamd_config:get_all_opt(N)
  1127. if opts and type(opts) == 'table' then
  1128. redis_params = rspamd_parse_redis_server(N)
  1129. for k,m in pairs(opts) do
  1130. if type(m) == 'table' and m['type'] then
  1131. local rule = add_multimap_rule(k, m)
  1132. if not rule then
  1133. rspamd_logger.errx(rspamd_config, 'cannot add rule: "'..k..'"')
  1134. else
  1135. rspamd_logger.infox(rspamd_config, 'added multimap rule: %s (%s)',
  1136. k, rule.type)
  1137. table.insert(rules, rule)
  1138. end
  1139. end
  1140. end
  1141. -- add fake symbol to check all maps inside a single callback
  1142. fun.each(function(rule)
  1143. local id = rspamd_config:register_symbol({
  1144. type = 'normal',
  1145. name = rule['symbol'],
  1146. callback = gen_multimap_callback(rule),
  1147. })
  1148. if rule['symbols'] then
  1149. -- Find allowed symbols by this map
  1150. rule['symbols_set'] = {}
  1151. fun.each(function(s)
  1152. rspamd_config:register_symbol({
  1153. type = 'virtual',
  1154. name = s,
  1155. parent = id,
  1156. score = tonumber(rule.score or "0") or 0, -- Default score
  1157. })
  1158. rule['symbols_set'][s] = 1
  1159. end, rule['symbols'])
  1160. end
  1161. if not rule.score then
  1162. rspamd_logger.infox(rspamd_config, 'set default score 0 for multimap rule %s', rule.symbol)
  1163. rule.score = 0
  1164. end
  1165. if rule.score then
  1166. -- Register metric symbol
  1167. rule.name = rule.symbol
  1168. rule.description = rule.description or 'multimap symbol'
  1169. rule.group = rule.group or N
  1170. local tmp_flags
  1171. tmp_flags = rule.flags
  1172. if rule.type == 'received' and rule.flags then
  1173. -- XXX: hack to allow received flags/nflags
  1174. -- See issue #3526 on GH
  1175. rule.flags = nil
  1176. end
  1177. -- XXX: for combined maps we use trace, so flags must include one_shot to avoid scores multiplication
  1178. if rule.combined and not rule.flags then
  1179. rule.flags = 'one_shot'
  1180. end
  1181. rspamd_config:set_metric_symbol(rule)
  1182. rule.flags = tmp_flags
  1183. end
  1184. end, fun.filter(function(r) return not r['prefilter'] end, rules))
  1185. -- prefilter symbols
  1186. fun.each(function(rule)
  1187. rspamd_config:register_symbol({
  1188. type = 'prefilter',
  1189. name = rule['symbol'],
  1190. score = tonumber(rule.score or "0") or 0,
  1191. callback = gen_multimap_callback(rule),
  1192. })
  1193. end, fun.filter(function(r) return r['prefilter'] end, rules))
  1194. if #rules == 0 then
  1195. lua_util.disable_module(N, "config")
  1196. end
  1197. end