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.

settings.lua 36KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294
  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. -- This plugin implements user dynamic settings
  17. -- Settings documentation can be found here:
  18. -- https://rspamd.com/doc/configuration/settings.html
  19. local rspamd_logger = require "rspamd_logger"
  20. local lua_maps = require "lua_maps"
  21. local lua_util = require "lua_util"
  22. local rspamd_ip = require "rspamd_ip"
  23. local rspamd_regexp = require "rspamd_regexp"
  24. local lua_selectors = require "lua_selectors"
  25. local lua_settings = require "lua_settings"
  26. local ucl = require "ucl"
  27. local fun = require "fun"
  28. local rspamd_mempool = require "rspamd_mempool"
  29. local redis_params
  30. local settings = {}
  31. local N = "settings"
  32. local settings_initialized = false
  33. local max_pri = 0
  34. local module_sym_id -- Main module symbol
  35. local function apply_settings(task, to_apply, id, name)
  36. local cached_name = task:cache_get('settings_name')
  37. if cached_name then
  38. local cached_settings = task:cache_get('settings')
  39. rspamd_logger.warnx(task, "cannot apply settings rule %s (id=%s):" ..
  40. " settings has been already applied by rule %s (id=%s)",
  41. name, id, cached_name, cached_settings.id)
  42. return false
  43. end
  44. task:set_settings(to_apply)
  45. task:cache_set('settings', to_apply)
  46. task:cache_set('settings_name', name or 'unknown')
  47. if id then
  48. task:set_settings_id(id)
  49. end
  50. if to_apply['add_headers'] or to_apply['remove_headers'] then
  51. local rep = {
  52. add_headers = to_apply['add_headers'] or {},
  53. remove_headers = to_apply['remove_headers'] or {},
  54. }
  55. task:set_rmilter_reply(rep)
  56. end
  57. if to_apply.flags and type(to_apply.flags) == 'table' then
  58. for _,fl in ipairs(to_apply.flags) do
  59. task:set_flag(fl)
  60. end
  61. end
  62. if to_apply.symbols then
  63. -- Add symbols, specified in the settings
  64. if #to_apply.symbols > 0 then
  65. -- Array like symbols
  66. for _,val in ipairs(to_apply.symbols) do
  67. task:insert_result(val, 1.0)
  68. end
  69. else
  70. -- Object like symbols
  71. for k,v in pairs(to_apply.symbols) do
  72. if type(v) == 'table' then
  73. task:insert_result(k, v.score or 1.0, v.options or {})
  74. elseif tonumber(v) then
  75. task:insert_result(k, tonumber(v))
  76. end
  77. end
  78. end
  79. end
  80. if to_apply.subject then
  81. task:set_metric_subject(to_apply.subject)
  82. end
  83. -- E.g.
  84. -- messages = { smtp_message = "5.3.1 Go away" }
  85. if to_apply.messages and type(to_apply.messages) == 'table' then
  86. fun.each(function(category, message)
  87. task:append_message(message, category)
  88. end, to_apply.messages)
  89. end
  90. return true
  91. end
  92. -- Checks for overridden settings within query params and returns 3 values:
  93. -- * Apply element
  94. -- * Settings ID element if found
  95. -- * Priority of the settings according to the place where it is found
  96. --
  97. -- If no override has been found, it returns `false`
  98. local function check_query_settings(task)
  99. -- Try 'settings' attribute
  100. local settings_id = task:get_settings_id()
  101. local query_set = task:get_request_header('settings')
  102. if query_set then
  103. local parser = ucl.parser()
  104. local res,err = parser:parse_string(tostring(query_set))
  105. if res then
  106. if settings_id then
  107. rspamd_logger.warnx(task, "both settings-id '%s' and settings headers are presented, ignore settings-id; ",
  108. tostring(settings_id))
  109. end
  110. local settings_obj = parser:get_object()
  111. -- Treat as low priority
  112. return settings_obj,nil,1
  113. else
  114. rspamd_logger.errx(task, 'Parse error: %s', err)
  115. end
  116. end
  117. local query_maxscore = task:get_request_header('maxscore')
  118. local nset
  119. if query_maxscore then
  120. if settings_id then
  121. rspamd_logger.infox(task, "both settings id '%s' and maxscore '%s' headers are presented, merge them; " ..
  122. "settings id has priority",
  123. tostring(settings_id), tostring(query_maxscore))
  124. end
  125. -- We have score limits redefined by request
  126. local ms = tonumber(tostring(query_maxscore))
  127. if ms then
  128. nset = {
  129. actions = {
  130. reject = ms
  131. }
  132. }
  133. local query_softscore = task:get_request_header('softscore')
  134. if query_softscore then
  135. local ss = tonumber(tostring(query_softscore))
  136. nset.actions['add header'] = ss
  137. end
  138. if not settings_id then
  139. rspamd_logger.infox(task, 'apply maxscore = %s', nset.actions)
  140. -- Maxscore is low priority
  141. return nset, nil, 1
  142. end
  143. end
  144. end
  145. if settings_id and settings_initialized then
  146. local cached = lua_settings.settings_by_id(settings_id)
  147. if cached then
  148. local elt = cached.settings
  149. if elt['whitelist'] then
  150. elt['apply'] = {whitelist = true}
  151. end
  152. if elt.apply then
  153. if nset then
  154. elt.apply = lua_util.override_defaults(nset, elt.apply)
  155. end
  156. return elt.apply, cached, cached.priority or 1
  157. end
  158. else
  159. rspamd_logger.warnx(task, 'no settings id "%s" has been found', settings_id)
  160. if nset then
  161. rspamd_logger.infox(task, 'apply maxscore = %s', nset.actions)
  162. return nset, nil, 1
  163. end
  164. end
  165. else
  166. if nset then
  167. rspamd_logger.infox(task, 'apply maxscore = %s', nset.actions)
  168. return nset, nil, 1
  169. end
  170. end
  171. return false
  172. end
  173. local function check_addr_setting(expected, addr)
  174. local function check_specific_addr(elt)
  175. if expected.name then
  176. if lua_maps.rspamd_maybe_check_map(expected.name, elt.addr) then
  177. return true
  178. end
  179. end
  180. if expected.user then
  181. if lua_maps.rspamd_maybe_check_map(expected.user, elt.user) then
  182. return true
  183. end
  184. end
  185. if expected.domain and elt.domain then
  186. if lua_maps.rspamd_maybe_check_map(expected.domain, elt.domain) then
  187. return true
  188. end
  189. end
  190. if expected.regexp then
  191. if expected.regexp:match(elt.addr) then
  192. return true
  193. end
  194. end
  195. return false
  196. end
  197. for _, e in ipairs(addr) do
  198. if check_specific_addr(e) then
  199. return true
  200. end
  201. end
  202. return false
  203. end
  204. local function check_string_setting(expected, str)
  205. if expected.regexp then
  206. if expected.regexp:match(str) then
  207. return true
  208. end
  209. elseif expected.check then
  210. if lua_maps.rspamd_maybe_check_map(expected.check, str) then
  211. return true
  212. end
  213. end
  214. return false
  215. end
  216. local function check_ip_setting(expected, ip)
  217. if not expected[2] then
  218. if lua_maps.rspamd_maybe_check_map(expected[1], ip:to_string()) then
  219. return true
  220. end
  221. else
  222. if expected[2] ~= 0 then
  223. local nip = ip:apply_mask(expected[2])
  224. if nip and nip:to_string() == expected[1]:to_string() then
  225. return true
  226. end
  227. elseif ip:to_string() == expected[1]:to_string() then
  228. return true
  229. end
  230. end
  231. return false
  232. end
  233. local function check_map_setting(map, input)
  234. return map:get_key(input)
  235. end
  236. local function priority_to_string(pri)
  237. if pri then
  238. if pri >= 3 then
  239. return "high"
  240. elseif pri >= 2 then
  241. return "medium"
  242. end
  243. end
  244. return "low"
  245. end
  246. -- Check limit for a task
  247. local function check_settings(task)
  248. local function check_specific_setting(rule, matched)
  249. local res = false
  250. local function process_atom(atom)
  251. local elt = rule.checks[atom]
  252. if elt then
  253. local input = elt.extract(task)
  254. if not input then return false end
  255. if elt.check(input) then
  256. matched[#matched + 1] = atom
  257. return 1.0
  258. end
  259. else
  260. rspamd_logger.errx(task, 'error in settings: check %s is not defined!', atom)
  261. end
  262. return 0
  263. end
  264. res = rule.expression and rule.expression:process(process_atom)
  265. if res and res > 0 then
  266. if rule['whitelist'] then
  267. rule['apply'] = {whitelist = true}
  268. end
  269. return rule
  270. end
  271. return nil
  272. end
  273. -- Check if we have override as query argument
  274. local query_apply,id_elt,priority = check_query_settings(task)
  275. local function maybe_apply_query_settings()
  276. if query_apply then
  277. if id_elt then
  278. apply_settings(task, query_apply, id_elt.id, id_elt.name)
  279. rspamd_logger.infox(task, "applied settings id %s(%s); priority %s",
  280. id_elt.name, id_elt.id, priority_to_string(priority))
  281. else
  282. apply_settings(task, query_apply, nil, 'HTTP query')
  283. rspamd_logger.infox(task, "applied settings from query; priority %s",
  284. priority_to_string(priority))
  285. end
  286. end
  287. end
  288. local min_pri = 1
  289. if query_apply then
  290. if priority >= min_pri then
  291. -- Do not check lower or equal priorities
  292. min_pri = priority + 1
  293. end
  294. if priority > max_pri then
  295. -- Our internal priorities are lower then a priority from query, so no need to check
  296. maybe_apply_query_settings()
  297. return
  298. end
  299. end
  300. -- Do not waste resources
  301. if not settings_initialized then
  302. maybe_apply_query_settings()
  303. return
  304. end
  305. -- Match rules according their order
  306. local applied = false
  307. for pri = max_pri,min_pri,-1 do
  308. if not applied and settings[pri] then
  309. for _,s in ipairs(settings[pri]) do
  310. local matched = {}
  311. lua_util.debugm(N, task, "check for settings element %s",
  312. s.name)
  313. local result = check_specific_setting(s.rule, matched)
  314. -- Can use xor here but more complicated for reading
  315. if result then
  316. if s.rule['apply'] then
  317. if s.rule.id then
  318. -- Extract static settings
  319. local cached = lua_settings.settings_by_id(s.rule.id)
  320. if not cached or not cached.settings or not cached.settings.apply then
  321. rspamd_logger.errx(task, 'unregistered settings id found: %s!', s.rule.id)
  322. else
  323. rspamd_logger.infox(task, "<%s> apply static settings %s (id = %s); %s matched; priority %s",
  324. task:get_message_id(),
  325. cached.name, s.rule.id,
  326. table.concat(matched, ','),
  327. priority_to_string(pri))
  328. apply_settings(task, cached.settings.apply, s.rule.id, s.name)
  329. end
  330. else
  331. -- Dynamic settings
  332. rspamd_logger.infox(task, "<%s> apply settings according to rule %s (%s matched)",
  333. task:get_message_id(), s.name, table.concat(matched, ','))
  334. apply_settings(task, s.rule.apply, nil, s.name)
  335. end
  336. applied = true
  337. end
  338. if s.rule['symbols'] then
  339. -- Add symbols, specified in the settings
  340. fun.each(function(val)
  341. task:insert_result(val, 1.0)
  342. end, s.rule['symbols'])
  343. end
  344. end
  345. end
  346. end
  347. end
  348. if not applied then
  349. maybe_apply_query_settings()
  350. end
  351. end
  352. local function convert_to_table(chk_elt, out)
  353. if type(chk_elt) == 'string' then
  354. return {out}
  355. end
  356. return out
  357. end
  358. -- Process IP address: converted to a table {ip, mask}
  359. local function process_ip_condition(ip)
  360. local out = {}
  361. if type(ip) == "table" then
  362. for _,v in ipairs(ip) do
  363. table.insert(out, process_ip_condition(v))
  364. end
  365. elseif type(ip) == "string" then
  366. local slash = string.find(ip, '/')
  367. if not slash then
  368. -- Just a plain IP address
  369. local res = rspamd_ip.from_string(ip)
  370. if res:is_valid() then
  371. out[1] = res
  372. out[2] = 0
  373. else
  374. -- It can still be a map
  375. out[1] = res
  376. end
  377. else
  378. local res = rspamd_ip.from_string(string.sub(ip, 1, slash - 1))
  379. local mask = tonumber(string.sub(ip, slash + 1))
  380. if res:is_valid() then
  381. out[1] = res
  382. out[2] = mask
  383. else
  384. rspamd_logger.errx(rspamd_config, "bad IP address: " .. ip)
  385. return nil
  386. end
  387. end
  388. else
  389. return nil
  390. end
  391. return out
  392. end
  393. -- Process email like condition, converted to a table with fields:
  394. -- name - full email (surprise!)
  395. -- user - user part
  396. -- domain - domain part
  397. -- regexp - full email regexp (yes, it sucks)
  398. local function process_email_condition(addr)
  399. local out = {}
  400. if type(addr) == "table" then
  401. for _,v in ipairs(addr) do
  402. table.insert(out, process_email_condition(v))
  403. end
  404. elseif type(addr) == "string" then
  405. if string.sub(addr, 1, 4) == "map:" then
  406. -- It is map, don't apply any extra logic
  407. out['name'] = addr
  408. else
  409. local start = string.sub(addr, 1, 1)
  410. if start == '/' then
  411. -- It is a regexp
  412. local re = rspamd_regexp.create(addr)
  413. if re then
  414. out['regexp'] = re
  415. else
  416. rspamd_logger.errx(rspamd_config, "bad regexp: " .. addr)
  417. return nil
  418. end
  419. elseif start == '@' then
  420. -- It is a domain if form @domain
  421. out['domain'] = string.sub(addr, 2)
  422. else
  423. -- Check user@domain parts
  424. local at = string.find(addr, '@')
  425. if at then
  426. -- It is full address
  427. out['name'] = addr
  428. else
  429. -- It is a user
  430. out['user'] = addr
  431. end
  432. end
  433. end
  434. else
  435. return nil
  436. end
  437. return out
  438. end
  439. -- Convert a plain string condition to a table:
  440. -- check - string to match
  441. -- regexp - regexp to match
  442. local function process_string_condition(addr)
  443. local out = {}
  444. if type(addr) == "table" then
  445. for _,v in ipairs(addr) do
  446. table.insert(out, process_string_condition(v))
  447. end
  448. elseif type(addr) == "string" then
  449. if string.sub(addr, 1, 4) == "map:" then
  450. -- It is map, don't apply any extra logic
  451. out['check'] = addr
  452. else
  453. local start = string.sub(addr, 1, 1)
  454. if start == '/' then
  455. -- It is a regexp
  456. local re = rspamd_regexp.create(addr)
  457. if re then
  458. out['regexp'] = re
  459. else
  460. rspamd_logger.errx(rspamd_config, "bad regexp: " .. addr)
  461. return nil
  462. end
  463. else
  464. out['check'] = addr
  465. end
  466. end
  467. else
  468. return nil
  469. end
  470. return out
  471. end
  472. local function get_priority (elt)
  473. local pri_tonum = function(p)
  474. if p then
  475. if type(p) == "number" then
  476. return tonumber(p)
  477. elseif type(p) == "string" then
  478. if p == "high" then
  479. return 3
  480. elseif p == "medium" then
  481. return 2
  482. end
  483. end
  484. end
  485. return 1
  486. end
  487. return pri_tonum(elt['priority'])
  488. end
  489. -- Used to create a checking closure: if value matches expected somehow, return true
  490. local function gen_check_closure(expected, check_func)
  491. return function(value)
  492. if not value then return false end
  493. if type(value) == 'function' then
  494. value = value()
  495. end
  496. if value then
  497. if not check_func then
  498. check_func = function(a, b) return a == b end
  499. end
  500. local ret
  501. if type(expected) == 'table' then
  502. ret = fun.any(function(d)
  503. return check_func(d, value)
  504. end, expected)
  505. else
  506. ret = check_func(expected, value)
  507. end
  508. if ret then
  509. return true
  510. end
  511. end
  512. return false
  513. end
  514. end
  515. -- Process settings based on their priority
  516. local function process_settings_table(tbl, allow_ids, mempool, is_static)
  517. -- Check the setting element internal data
  518. local process_setting_elt = function(name, elt)
  519. lua_util.debugm(N, rspamd_config, 'process settings "%s"', name)
  520. local out = {}
  521. local checks = {}
  522. if elt.ip then
  523. local ips_table = process_ip_condition(elt['ip'])
  524. if ips_table then
  525. lua_util.debugm(N, rspamd_config, 'added ip condition to "%s": %s',
  526. name, ips_table)
  527. checks.ip = {
  528. check = gen_check_closure(convert_to_table(elt.ip, ips_table), check_ip_setting),
  529. extract = function(task)
  530. local ip = task:get_from_ip()
  531. if ip and ip:is_valid() then return ip end
  532. return nil
  533. end,
  534. }
  535. end
  536. end
  537. if elt.ip_map then
  538. local ips_map = lua_maps.map_add_from_ucl(elt.ip_map, 'radix',
  539. 'settings ip map for ' .. name)
  540. if ips_map then
  541. lua_util.debugm(N, rspamd_config, 'added ip_map condition to "%s"',
  542. name)
  543. checks.ip_map = {
  544. check = gen_check_closure(ips_map, check_map_setting),
  545. extract = function(task)
  546. local ip = task:get_from_ip()
  547. if ip and ip:is_valid() then return ip end
  548. return nil
  549. end,
  550. }
  551. end
  552. end
  553. if elt.client_ip then
  554. local client_ips_table = process_ip_condition(elt.client_ip)
  555. if client_ips_table then
  556. lua_util.debugm(N, rspamd_config, 'added client_ip condition to "%s": %s',
  557. name, client_ips_table)
  558. checks.client_ip = {
  559. check = gen_check_closure(convert_to_table(elt.client_ip, client_ips_table),
  560. check_ip_setting),
  561. extract = function(task)
  562. local ip = task:get_client_ip()
  563. if ip:is_valid() then return ip end
  564. return nil
  565. end,
  566. }
  567. end
  568. end
  569. if elt.client_ip_map then
  570. local ips_map = lua_maps.map_add_from_ucl(elt.ip_map, 'radix',
  571. 'settings client ip map for ' .. name)
  572. if ips_map then
  573. lua_util.debugm(N, rspamd_config, 'added client ip_map condition to "%s"',
  574. name)
  575. checks.client_ip_map = {
  576. check = gen_check_closure(ips_map, check_map_setting),
  577. extract = function(task)
  578. local ip = task:get_client_ip()
  579. if ip and ip:is_valid() then return ip end
  580. return nil
  581. end,
  582. }
  583. end
  584. end
  585. if elt.from then
  586. local from_condition = process_email_condition(elt.from)
  587. if from_condition then
  588. lua_util.debugm(N, rspamd_config, 'added from condition to "%s": %s',
  589. name, from_condition)
  590. checks.from = {
  591. check = gen_check_closure(convert_to_table(elt.from, from_condition),
  592. check_addr_setting),
  593. extract = function(task)
  594. return task:get_from(1)
  595. end,
  596. }
  597. end
  598. end
  599. if elt.rcpt then
  600. local rcpt_condition = process_email_condition(elt.rcpt)
  601. if rcpt_condition then
  602. lua_util.debugm(N, rspamd_config, 'added rcpt condition to "%s": %s',
  603. name, rcpt_condition)
  604. checks.rcpt = {
  605. check = gen_check_closure(convert_to_table(elt.rcpt, rcpt_condition),
  606. check_addr_setting),
  607. extract = function(task)
  608. return task:get_recipients(1)
  609. end,
  610. }
  611. end
  612. end
  613. if elt.from_mime then
  614. local from_mime_condition = process_email_condition(elt.from_mime)
  615. if from_mime_condition then
  616. lua_util.debugm(N, rspamd_config, 'added from_mime condition to "%s": %s',
  617. name, from_mime_condition)
  618. checks.from_mime = {
  619. check = gen_check_closure(convert_to_table(elt.from_mime, from_mime_condition),
  620. check_addr_setting),
  621. extract = function(task)
  622. return task:get_from(2)
  623. end,
  624. }
  625. end
  626. end
  627. if elt.rcpt_mime then
  628. local rcpt_mime_condition = process_email_condition(elt.rcpt_mime)
  629. if rcpt_mime_condition then
  630. lua_util.debugm(N, rspamd_config, 'added rcpt mime condition to "%s": %s',
  631. name, rcpt_mime_condition)
  632. checks.rcpt_mime = {
  633. check = gen_check_closure(convert_to_table(elt.rcpt_mime, rcpt_mime_condition),
  634. check_addr_setting),
  635. extract = function(task)
  636. return task:get_recipients(2)
  637. end,
  638. }
  639. end
  640. end
  641. if elt.user then
  642. local user_condition = process_email_condition(elt.user)
  643. if user_condition then
  644. lua_util.debugm(N, rspamd_config, 'added user condition to "%s": %s',
  645. name, user_condition)
  646. checks.user = {
  647. check = gen_check_closure(convert_to_table(elt.user, user_condition),
  648. check_addr_setting),
  649. extract = function(task)
  650. local uname = task:get_user()
  651. local user = {}
  652. if uname then
  653. user[1] = {}
  654. local localpart, domainpart = string.gmatch(uname, "(.+)@(.+)")()
  655. if localpart then
  656. user[1]["user"] = localpart
  657. user[1]["domain"] = domainpart
  658. user[1]["addr"] = uname
  659. else
  660. user[1]["user"] = uname
  661. user[1]["addr"] = uname
  662. end
  663. return user
  664. end
  665. return nil
  666. end,
  667. }
  668. end
  669. end
  670. if elt.hostname then
  671. local hostname_condition = process_string_condition(elt.hostname)
  672. if hostname_condition then
  673. lua_util.debugm(N, rspamd_config, 'added hostname condition to "%s": %s',
  674. name, hostname_condition)
  675. checks.hostname = {
  676. check = gen_check_closure(convert_to_table(elt.hostname, hostname_condition),
  677. check_string_setting),
  678. extract = function(task)
  679. return task:get_hostname() or ''
  680. end,
  681. }
  682. end
  683. end
  684. if elt.authenticated then
  685. lua_util.debugm(N, rspamd_config, 'added authenticated condition to "%s"',
  686. name)
  687. checks.authenticated = {
  688. check = function(value) if value then return true end return false end,
  689. extract = function(task)
  690. return task:get_user()
  691. end
  692. }
  693. end
  694. if elt['local'] then
  695. lua_util.debugm(N, rspamd_config, 'added local condition to "%s"',
  696. name)
  697. checks['local'] = {
  698. check = function(value) if value then return true end return false end,
  699. extract = function(task)
  700. local ip = task:get_from_ip()
  701. if not ip or not ip:is_valid() then
  702. return nil
  703. end
  704. if ip:is_local() then
  705. return true
  706. else
  707. return nil
  708. end
  709. end
  710. }
  711. end
  712. local aliases = {}
  713. -- This function is used to convert compound condition with
  714. -- generic type and specific part (e.g. `header`, `Content-Transfer-Encoding`)
  715. -- to a set of usable check elements:
  716. -- `generic:specific` - most common part
  717. -- `generic:<order>` - e.g. `header:1` for the first header
  718. -- `generic:safe` - replace unsafe stuff with safe + lowercase
  719. -- also aliases entry is set to avoid implicit expression
  720. local function process_compound_condition(cond, generic, specific)
  721. local full_key = generic .. ':' .. specific
  722. checks[full_key] = cond
  723. -- Try numeric key
  724. for i=1,1000 do
  725. local num_key = generic .. ':' .. tostring(i)
  726. if not checks[num_key] then
  727. checks[num_key] = cond
  728. aliases[num_key] = true
  729. break
  730. end
  731. end
  732. local safe_key = generic .. ':' ..
  733. specific:gsub('[:%-+&|><]', '_')
  734. :gsub('%(', '[')
  735. :gsub('%)', ']')
  736. :lower()
  737. if not checks[safe_key] then
  738. checks[safe_key] = cond
  739. aliases[safe_key] = true
  740. end
  741. return safe_key
  742. end
  743. -- Headers are tricky:
  744. -- We create an closure with extraction function depending on header name
  745. -- We also inserts it into `checks` table as an atom in form header:<hname>
  746. -- Check function depends on the input:
  747. -- * for something that looks like `header = "/bar/"` we create a regexp
  748. -- * for something that looks like `header = true` we just check the existence
  749. local function process_header_elt(table_element, extractor_func)
  750. if elt[table_element] then
  751. for k, v in pairs(elt[table_element]) do
  752. if type(v) == 'string' then
  753. local re = rspamd_regexp.create(v)
  754. if re then
  755. local cond = {
  756. check = function(values)
  757. return fun.any(function(c) return re:match(c) end, values)
  758. end,
  759. extract = extractor_func(k),
  760. }
  761. local skey = process_compound_condition(cond, table_element,
  762. k)
  763. lua_util.debugm(N, rspamd_config, 'added %s condition to "%s": %s =~ %s',
  764. skey, name, k, v)
  765. end
  766. elseif type(v) == 'boolean' then
  767. local cond = {
  768. check = function(values)
  769. if #values == 0 then return (not v) end
  770. return v
  771. end,
  772. extract = extractor_func(k),
  773. }
  774. local skey = process_compound_condition(cond, table_element,
  775. k)
  776. lua_util.debugm(N, rspamd_config, 'added %s condition to "%s": %s == %s',
  777. skey, name, k, v)
  778. else
  779. rspamd_logger.errx(rspamd_config, 'invalid %s %s = %s', table_element, k, v)
  780. end
  781. end
  782. end
  783. end
  784. process_header_elt('request_header', function(hname)
  785. return function(task)
  786. local rh = task:get_request_header(hname)
  787. if rh then return {rh} end
  788. return {}
  789. end
  790. end)
  791. process_header_elt('header', function(hname)
  792. return function(task)
  793. local rh = task:get_header_full(hname)
  794. if rh then
  795. return fun.totable(fun.map(function(h) return h.decoded end, rh))
  796. end
  797. return {}
  798. end
  799. end)
  800. if elt.selector then
  801. local sel = lua_selectors.create_selector_closure(rspamd_config, elt.selector,
  802. elt.delimiter or "")
  803. if sel then
  804. local cond = {
  805. check = function(values)
  806. return fun.any(function(c)
  807. return c
  808. end, values)
  809. end,
  810. extract = sel,
  811. }
  812. local skey = process_compound_condition(cond, 'selector', elt.selector)
  813. lua_util.debugm(N, rspamd_config, 'added selector condition to "%s": %s',
  814. name, skey)
  815. end
  816. end
  817. -- Special, special case!
  818. local inverse = false
  819. if elt.inverse then
  820. lua_util.debugm(N, rspamd_config, 'added inverse condition to "%s"',
  821. name)
  822. inverse = true
  823. end
  824. -- Count checks and create Rspamd expression from a set of rules
  825. local nchecks = 0
  826. for _,_ in pairs(checks) do nchecks = nchecks + 1 end
  827. if nchecks > 0 then
  828. -- Now we can deal with the expression!
  829. if not elt.expression then
  830. -- Artificial & expression to deal with the legacy parts
  831. -- Here we get all keys and concatenate them with '&&'
  832. local s = ' && '
  833. -- By De Morgan laws
  834. if inverse then s = ' || ' end
  835. -- Exclude aliases and join all checks by key
  836. local expr_str = table.concat(lua_util.keys(fun.filter(
  837. function(k, _) return not aliases[k] end,
  838. checks)), s)
  839. if inverse then
  840. expr_str = string.format('!(%s)', expr_str)
  841. end
  842. elt.expression = expr_str
  843. lua_util.debugm(N, rspamd_config, 'added implicit settings expression for %s: %s',
  844. name, expr_str)
  845. end
  846. -- Parse expression's sanity
  847. local function parse_atom(str)
  848. local atom = table.concat(fun.totable(fun.take_while(function(c)
  849. if string.find(', \t()><+!|&\n', c) then
  850. return false
  851. end
  852. return true
  853. end, fun.iter(str))), '')
  854. if checks[atom] then
  855. return atom
  856. end
  857. rspamd_logger.errx(rspamd_config,
  858. 'use of undefined element "%s" when parsing settings expression, known checks: %s',
  859. atom, table.concat(fun.totable(fun.map(function(k, _) return k end, checks)), ','))
  860. return nil
  861. end
  862. local rspamd_expression = require "rspamd_expression"
  863. out.expression = rspamd_expression.create(elt.expression, parse_atom,
  864. mempool)
  865. out.checks = checks
  866. if not out.expression then
  867. rspamd_logger.errx(rspamd_config, 'cannot parse expression %s for %s',
  868. elt.expression, name)
  869. else
  870. lua_util.debugm(N, rspamd_config, 'registered settings %s with %s checks',
  871. name, nchecks)
  872. end
  873. else
  874. lua_util.debugm(N, rspamd_config, 'registered settings %s with no checks',
  875. name)
  876. end
  877. -- Process symbols part/apply part
  878. if elt['symbols'] then
  879. lua_util.debugm(N, rspamd_config, 'added symbols condition to "%s": %s',
  880. name, elt.symbols)
  881. out['symbols'] = elt['symbols']
  882. end
  883. if elt['apply'] then
  884. -- Just insert all metric results to the action key
  885. out['apply'] = elt['apply']
  886. elseif elt['whitelist'] or elt['want_spam'] then
  887. out['whitelist'] = true
  888. else
  889. rspamd_logger.errx(rspamd_config, "no actions in settings: " .. name)
  890. return nil
  891. end
  892. if allow_ids then
  893. if not elt.id then
  894. elt.id = name
  895. end
  896. if elt['id'] then
  897. -- We are here from a postload script
  898. out.id = lua_settings.register_settings_id(elt.id, out, true)
  899. lua_util.debugm(N, rspamd_config,
  900. 'added settings id to "%s": %s -> %s',
  901. name, elt.id, out.id)
  902. end
  903. if not is_static then
  904. -- If we apply that from map
  905. -- In fact, it is useless and evil but who cares...
  906. if elt.apply and elt.apply.symbols then
  907. -- Register virtual symbols
  908. for k,v in pairs(elt.apply.symbols) do
  909. local rtb = {
  910. type = 'virtual',
  911. parent = module_sym_id,
  912. }
  913. if type(k) == 'number' and type(v) == 'string' then
  914. rtb.name = v
  915. elseif type(k) == 'string' then
  916. rtb.name = k
  917. end
  918. if out.id then
  919. rtb.allowed_ids = tostring(elt.id)
  920. end
  921. rspamd_config:register_symbol(rtb)
  922. end
  923. end
  924. end
  925. else
  926. if elt['id'] then
  927. rspamd_logger.errx(rspamd_config,
  928. 'cannot set static IDs from dynamic settings, please read the docs')
  929. end
  930. end
  931. return out
  932. end
  933. settings_initialized = false
  934. -- filter trash in the input
  935. local ft = fun.filter(
  936. function(_, elt)
  937. if type(elt) == "table" then
  938. return true
  939. end
  940. return false
  941. end, tbl)
  942. -- clear all settings
  943. max_pri = 0
  944. local nrules = 0
  945. for k in pairs(settings) do settings[k]={} end
  946. -- fill new settings by priority
  947. fun.for_each(function(k, v)
  948. local pri = get_priority(v)
  949. if pri > max_pri then max_pri = pri end
  950. if not settings[pri] then
  951. settings[pri] = {}
  952. end
  953. local s = process_setting_elt(k, v)
  954. if s then
  955. table.insert(settings[pri], {name = k, rule = s})
  956. nrules = nrules + 1
  957. end
  958. end, ft)
  959. -- sort settings with equal priorities in alphabetical order
  960. for pri,_ in pairs(settings) do
  961. table.sort(settings[pri], function(a,b) return a.name < b.name end)
  962. end
  963. settings_initialized = true
  964. lua_settings.load_all_settings(true)
  965. rspamd_logger.infox(rspamd_config, 'loaded %1 elements of settings', nrules)
  966. return true
  967. end
  968. -- Parse settings map from the ucl line
  969. local settings_map_pool
  970. local function process_settings_map(map_text)
  971. local parser = ucl.parser()
  972. local res,err
  973. if type(map_text) == 'string' then
  974. res,err = parser:parse_string(map_text)
  975. else
  976. res,err = parser:parse_text(map_text)
  977. end
  978. if not res then
  979. rspamd_logger.warnx(rspamd_config, 'cannot parse settings map: ' .. err)
  980. else
  981. if settings_map_pool then
  982. settings_map_pool:destroy()
  983. end
  984. settings_map_pool = rspamd_mempool.create()
  985. local obj = parser:get_object()
  986. if obj['settings'] then
  987. process_settings_table(obj['settings'], false,
  988. settings_map_pool, false)
  989. else
  990. process_settings_table(obj, false, settings_map_pool,
  991. false)
  992. end
  993. end
  994. return res
  995. end
  996. local function gen_redis_callback(handler, id)
  997. return function(task)
  998. local key = handler(task)
  999. local function redis_settings_cb(err, data)
  1000. if not err and type(data) == 'table' then
  1001. for _, d in ipairs(data) do
  1002. if type(d) == 'string' then
  1003. local parser = ucl.parser()
  1004. local res,ucl_err = parser:parse_string(d)
  1005. if not res then
  1006. rspamd_logger.warnx(rspamd_config, 'cannot parse settings from redis: %s',
  1007. ucl_err)
  1008. else
  1009. local obj = parser:get_object()
  1010. rspamd_logger.infox(task, "<%1> apply settings according to redis rule %2",
  1011. task:get_message_id(), id)
  1012. apply_settings(task, obj, nil, 'redis')
  1013. break
  1014. end
  1015. end
  1016. end
  1017. elseif err then
  1018. rspamd_logger.errx(task, 'Redis error: %1', err)
  1019. end
  1020. end
  1021. if not key then
  1022. lua_util.debugm(N, task, 'handler number %s returned nil', id)
  1023. return
  1024. end
  1025. local keys
  1026. if type(key) == 'table' then
  1027. keys = key
  1028. else
  1029. keys = {key}
  1030. end
  1031. key = keys[1]
  1032. local ret,_,_ = rspamd_redis_make_request(task,
  1033. redis_params, -- connect params
  1034. key, -- hash key
  1035. false, -- is write
  1036. redis_settings_cb, --callback
  1037. 'MGET', -- command
  1038. keys -- arguments
  1039. )
  1040. if not ret then
  1041. rspamd_logger.errx(task, 'Redis MGET failed: %s', ret)
  1042. end
  1043. end
  1044. end
  1045. local redis_section = rspamd_config:get_all_opt("settings_redis")
  1046. local redis_key_handlers = {}
  1047. if redis_section then
  1048. redis_params = rspamd_parse_redis_server('settings_redis')
  1049. if redis_params then
  1050. local handlers = redis_section.handlers
  1051. for id,h in pairs(handlers) do
  1052. local chunk,err = load(h)
  1053. if not chunk then
  1054. rspamd_logger.errx(rspamd_config, 'Cannot load handler from string: %s',
  1055. tostring(err))
  1056. else
  1057. local res,func = pcall(chunk)
  1058. if not res then
  1059. rspamd_logger.errx(rspamd_config, 'Cannot add handler from string: %s',
  1060. tostring(func))
  1061. else
  1062. redis_key_handlers[id] = func
  1063. end
  1064. end
  1065. end
  1066. end
  1067. fun.each(function(id, h)
  1068. rspamd_config:register_symbol({
  1069. name = 'REDIS_SETTINGS' .. tostring(id),
  1070. type = 'prefilter',
  1071. callback = gen_redis_callback(h, id),
  1072. priority = 10,
  1073. flags = 'empty,nostat',
  1074. })
  1075. end, redis_key_handlers)
  1076. end
  1077. module_sym_id = rspamd_config:register_symbol({
  1078. name = 'SETTINGS_CHECK',
  1079. type = 'prefilter',
  1080. callback = check_settings,
  1081. priority = 10,
  1082. flags = 'empty,nostat,explicit_disable,ignore_passthrough',
  1083. })
  1084. local set_section = rspamd_config:get_all_opt("settings")
  1085. if set_section and set_section[1] and type(set_section[1]) == "string" then
  1086. -- Just a map of ucl
  1087. local map_attrs = {
  1088. url = set_section[1],
  1089. description = "settings map",
  1090. callback = process_settings_map,
  1091. opaque_data = true
  1092. }
  1093. if not rspamd_config:add_map(map_attrs) then
  1094. rspamd_logger.errx(rspamd_config, 'cannot load settings from %1', set_section)
  1095. end
  1096. elseif set_section and type(set_section) == "table" then
  1097. settings_map_pool = rspamd_mempool.create()
  1098. -- We need to check this table and register static symbols first
  1099. -- Postponed settings init is needed to ensure that all symbols have been
  1100. -- registered BEFORE settings plugin. Otherwise, we can have inconsistent settings expressions
  1101. fun.each(function(_, elt)
  1102. if elt.apply and elt.apply.symbols then
  1103. -- Register virtual symbols
  1104. for k,v in pairs(elt.apply.symbols) do
  1105. local rtb = {
  1106. type = 'virtual',
  1107. parent = module_sym_id,
  1108. }
  1109. if type(k) == 'number' and type(v) == 'string' then
  1110. rtb.name = v
  1111. elseif type(k) == 'string' then
  1112. rtb.name = k
  1113. end
  1114. rspamd_config:register_symbol(rtb)
  1115. end
  1116. end
  1117. end,
  1118. -- Include only settings, exclude all maps
  1119. fun.filter(
  1120. function(_, elt)
  1121. if type(elt) == "table" then
  1122. return true
  1123. end
  1124. return false
  1125. end, set_section)
  1126. )
  1127. rspamd_config:add_post_init(function ()
  1128. process_settings_table(set_section, true, settings_map_pool, true)
  1129. end, 100)
  1130. end
  1131. rspamd_config:add_config_unload(function()
  1132. if settings_map_pool then
  1133. settings_map_pool:destroy()
  1134. end
  1135. end)