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.

dmarc.lua 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  3. Copyright (c) 2015-2016, 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. -- Dmarc policy filter
  15. local rspamd_logger = require "rspamd_logger"
  16. local rspamd_util = require "rspamd_util"
  17. local lua_redis = require "lua_redis"
  18. local lua_util = require "lua_util"
  19. local dmarc_common = require "plugins/dmarc"
  20. if confighelp then
  21. return
  22. end
  23. local N = 'dmarc'
  24. local settings = dmarc_common.default_settings
  25. local redis_params = nil
  26. local E = {}
  27. -- Keys:
  28. -- 1 = index key (string)
  29. -- 2 = report key (string)
  30. -- 3 = max report elements (number)
  31. -- 4 = expiry time for elements (number)
  32. -- Arguments
  33. -- 1 = dmarc domain
  34. -- 2 = dmarc report
  35. local take_report_id
  36. local take_report_script = [[
  37. local index_key = KEYS[1]
  38. local report_key = KEYS[2]
  39. local max_entries = -(tonumber(KEYS[3]) + 1)
  40. local keys_expiry = tonumber(KEYS[4])
  41. local dmarc_domain = ARGV[1]
  42. local report = ARGV[2]
  43. redis.call('SADD', index_key, report_key)
  44. redis.call('EXPIRE', index_key, 172800)
  45. redis.call('ZINCRBY', report_key, 1, report)
  46. redis.call('ZREMRANGEBYRANK', report_key, 0, max_entries)
  47. redis.call('EXPIRE', report_key, 172800)
  48. ]]
  49. local function maybe_force_action(task, disposition)
  50. if disposition then
  51. local force_action = settings.actions[disposition]
  52. if force_action then
  53. -- Set least action
  54. task:set_pre_result(force_action, 'Action set by DMARC', N, nil, nil, 'least')
  55. end
  56. end
  57. end
  58. local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
  59. local reason = {}
  60. -- Check dkim and spf symbols
  61. local spf_ok = false
  62. local dkim_ok = false
  63. local spf_tmpfail = false
  64. local dkim_tmpfail = false
  65. local spf_domain = ((task:get_from(1) or E)[1] or E).domain
  66. if not spf_domain or spf_domain == '' then
  67. spf_domain = task:get_helo() or ''
  68. end
  69. if task:has_symbol(settings.symbols['spf_allow_symbol']) then
  70. if policy.strict_spf then
  71. if rspamd_util.strequal_caseless(spf_domain, hdrfromdom) then
  72. spf_ok = true
  73. else
  74. table.insert(reason, "SPF not aligned (strict)")
  75. end
  76. else
  77. local spf_tld = rspamd_util.get_tld(spf_domain)
  78. if rspamd_util.strequal_caseless(spf_tld, dmarc_esld) then
  79. spf_ok = true
  80. else
  81. table.insert(reason, "SPF not aligned (relaxed)")
  82. end
  83. end
  84. else
  85. if task:has_symbol(settings.symbols['spf_tempfail_symbol']) then
  86. if policy.strict_spf then
  87. if rspamd_util.strequal_caseless(spf_domain, hdrfromdom) then
  88. spf_tmpfail = true
  89. end
  90. else
  91. local spf_tld = rspamd_util.get_tld(spf_domain)
  92. if rspamd_util.strequal_caseless(spf_tld, dmarc_esld) then
  93. spf_tmpfail = true
  94. end
  95. end
  96. end
  97. table.insert(reason, "No valid SPF")
  98. end
  99. local opts = ((task:get_symbol('DKIM_TRACE') or E)[1] or E).options
  100. local dkim_results = {
  101. pass = {},
  102. temperror = {},
  103. permerror = {},
  104. fail = {},
  105. }
  106. if opts then
  107. dkim_results.pass = {}
  108. local dkim_violated
  109. for _,opt in ipairs(opts) do
  110. local check_res = string.sub(opt, -1)
  111. local domain = string.sub(opt, 1, -3):lower()
  112. if check_res == '+' then
  113. table.insert(dkim_results.pass, domain)
  114. if policy.strict_dkim then
  115. if rspamd_util.strequal_caseless(hdrfromdom, domain) then
  116. dkim_ok = true
  117. else
  118. dkim_violated = "DKIM not aligned (strict)"
  119. end
  120. else
  121. local dkim_tld = rspamd_util.get_tld(domain)
  122. if rspamd_util.strequal_caseless(dkim_tld, dmarc_esld) then
  123. dkim_ok = true
  124. else
  125. dkim_violated = "DKIM not aligned (relaxed)"
  126. end
  127. end
  128. elseif check_res == '?' then
  129. -- Check for dkim tempfail
  130. if not dkim_ok then
  131. if policy.strict_dkim then
  132. if rspamd_util.strequal_caseless(hdrfromdom, domain) then
  133. dkim_tmpfail = true
  134. end
  135. else
  136. local dkim_tld = rspamd_util.get_tld(domain)
  137. if rspamd_util.strequal_caseless(dkim_tld, dmarc_esld) then
  138. dkim_tmpfail = true
  139. end
  140. end
  141. end
  142. table.insert(dkim_results.temperror, domain)
  143. elseif check_res == '-' then
  144. table.insert(dkim_results.fail, domain)
  145. else
  146. table.insert(dkim_results.permerror, domain)
  147. end
  148. end
  149. if not dkim_ok and dkim_violated then
  150. table.insert(reason, dkim_violated)
  151. end
  152. else
  153. table.insert(reason, "No valid DKIM")
  154. end
  155. lua_util.debugm(N, task,
  156. "validated dmarc policy for %s: %s; dkim_ok=%s, dkim_tempfail=%s, spf_ok=%s, spf_tempfail=%s",
  157. policy.domain, policy.dmarc_policy,
  158. dkim_ok, dkim_tmpfail,
  159. spf_ok, spf_tmpfail)
  160. local disposition = 'none'
  161. local sampled_out = false
  162. local function handle_dmarc_failure(what, reason_str)
  163. if not policy.pct or policy.pct == 100 then
  164. task:insert_result(settings.symbols[what], 1.0,
  165. policy.domain .. ' : ' .. reason_str, policy.dmarc_policy)
  166. disposition = what
  167. else
  168. local coin = math.random(100)
  169. if (coin > policy.pct) then
  170. if (not settings.no_sampling_domains or
  171. not settings.no_sampling_domains:get_key(policy.domain)) then
  172. if what == 'reject' then
  173. disposition = 'quarantine'
  174. else
  175. disposition = 'softfail'
  176. end
  177. task:insert_result(settings.symbols[disposition], 1.0,
  178. policy.domain .. ' : ' .. reason_str, policy.dmarc_policy, "sampled_out")
  179. sampled_out = true
  180. lua_util.debugm(N, task,
  181. 'changed dmarc policy from %s to %s, sampled out: %s < %s',
  182. what, disposition, coin, policy.pct)
  183. else
  184. task:insert_result(settings.symbols[what], 1.0,
  185. policy.domain .. ' : ' .. reason_str, policy.dmarc_policy, "local_policy")
  186. disposition = what
  187. end
  188. else
  189. task:insert_result(settings.symbols[what], 1.0,
  190. policy.domain .. ' : ' .. reason_str, policy.dmarc_policy)
  191. disposition = what
  192. end
  193. end
  194. maybe_force_action(task, disposition)
  195. end
  196. if spf_ok or dkim_ok then
  197. --[[
  198. https://tools.ietf.org/html/rfc7489#section-6.6.2
  199. DMARC evaluation can only yield a "pass" result after one of the
  200. underlying authentication mechanisms passes for an aligned
  201. identifier.
  202. ]]--
  203. task:insert_result(settings.symbols['allow'], 1.0, policy.domain,
  204. policy.dmarc_policy)
  205. else
  206. --[[
  207. https://tools.ietf.org/html/rfc7489#section-6.6.2
  208. If neither passes and one or both of them fail due to a
  209. temporary error, the Receiver evaluating the message is unable to
  210. conclude that the DMARC mechanism had a permanent failure; they
  211. therefore cannot apply the advertised DMARC policy.
  212. ]]--
  213. if spf_tmpfail or dkim_tmpfail then
  214. task:insert_result(settings.symbols['dnsfail'], 1.0, policy.domain..
  215. ' : ' .. 'SPF/DKIM temp error', policy.dmarc_policy)
  216. else
  217. -- We can now check the failed policy and maybe send report data elt
  218. local reason_str = table.concat(reason, ', ')
  219. if policy.dmarc_policy == 'quarantine' then
  220. handle_dmarc_failure('quarantine', reason_str)
  221. elseif policy.dmarc_policy == 'reject' then
  222. handle_dmarc_failure('reject', reason_str)
  223. else
  224. task:insert_result(settings.symbols['softfail'], 1.0,
  225. policy.domain .. ' : ' .. reason_str,
  226. policy.dmarc_policy)
  227. end
  228. end
  229. end
  230. if policy.rua and redis_params and settings.reporting.enabled then
  231. if settings.no_reporting_domains then
  232. if settings.no_reporting_domains:get_key(policy.domain) or
  233. settings.no_reporting_domains:get_key(rspamd_util.get_tld(policy.domain)) then
  234. rspamd_logger.infox(task, 'DMARC reporting suppressed for %s', policy.domain)
  235. return
  236. end
  237. end
  238. local function dmarc_report_cb(err)
  239. if not err then
  240. rspamd_logger.infox(task, 'dmarc report saved for %s (rua = %s)',
  241. hdrfromdom, policy.rua)
  242. else
  243. rspamd_logger.errx(task, 'dmarc report is not saved for %s: %s',
  244. hdrfromdom, err)
  245. end
  246. end
  247. local spf_result
  248. if spf_ok then
  249. spf_result = 'pass'
  250. elseif spf_tmpfail then
  251. spf_result = 'temperror'
  252. else
  253. if task:has_symbol(settings.symbols.spf_deny_symbol) then
  254. spf_result = 'fail'
  255. elseif task:has_symbol(settings.symbols.spf_softfail_symbol) then
  256. spf_result = 'softfail'
  257. elseif task:has_symbol(settings.symbols.spf_neutral_symbol) then
  258. spf_result = 'neutral'
  259. elseif task:has_symbol(settings.symbols.spf_permfail_symbol) then
  260. spf_result = 'permerror'
  261. else
  262. spf_result = 'none'
  263. end
  264. end
  265. -- Prepare and send redis report element
  266. local period = os.date('!%Y%m%d',
  267. task:get_date({format = 'connect', gmt = true}))
  268. -- Dmarc domain key must include dmarc domain, rua and period
  269. local dmarc_domain_key = table.concat(
  270. {settings.reporting.redis_keys.report_prefix, dmarc_esld, policy.rua, period},
  271. settings.reporting.redis_keys.join_char)
  272. local report_data = dmarc_common.dmarc_report(task, settings, {
  273. spf_ok = spf_ok and 'pass' or 'fail',
  274. dkim_ok = dkim_ok and 'pass' or 'fail',
  275. disposition = (disposition == "softfail") and "none" or disposition,
  276. sampled_out = sampled_out,
  277. domain = hdrfromdom,
  278. spf_domain = spf_domain,
  279. dkim_results = dkim_results,
  280. spf_result = spf_result
  281. })
  282. local idx_key = table.concat({settings.reporting.redis_keys.index_prefix, period},
  283. settings.reporting.redis_keys.join_char)
  284. if report_data then
  285. lua_redis.exec_redis_script(take_report_id,
  286. {task = task, is_write = true},
  287. dmarc_report_cb,
  288. {idx_key, dmarc_domain_key,
  289. tostring(settings.reporting.max_entries), tostring(settings.reporting.keys_expire)},
  290. {hdrfromdom, report_data})
  291. end
  292. end
  293. end
  294. local function dmarc_callback(task)
  295. local from = task:get_from(2)
  296. local hfromdom = ((from or E)[1] or E).domain
  297. local dmarc_domain
  298. local ip_addr = task:get_ip()
  299. local dmarc_checks = task:get_mempool():get_variable('dmarc_checks', 'double') or 0
  300. local seen_invalid = false
  301. if dmarc_checks ~= 2 then
  302. rspamd_logger.infox(task, "skip DMARC checks as either SPF or DKIM were not checked")
  303. return
  304. end
  305. if lua_util.is_skip_local_or_authed(task, settings.auth_and_local_conf, ip_addr) then
  306. rspamd_logger.infox(task, "skip DMARC checks for local networks and authorized users")
  307. return
  308. end
  309. -- Do some initial sanity checks, detect tld domain if different
  310. if hfromdom and hfromdom ~= '' and not (from or E)[2] then
  311. -- Lowercase domain as per #3940
  312. hfromdom = hfromdom:lower()
  313. dmarc_domain = rspamd_util.get_tld(hfromdom)
  314. elseif (from or E)[2] then
  315. task:insert_result(settings.symbols['na'], 1.0, 'Duplicate From header')
  316. return maybe_force_action(task, 'na')
  317. elseif (from or E)[1] then
  318. task:insert_result(settings.symbols['na'], 1.0, 'No domain in From header')
  319. return maybe_force_action(task,'na')
  320. else
  321. task:insert_result(settings.symbols['na'], 1.0, 'No From header')
  322. return maybe_force_action(task,'na')
  323. end
  324. local dns_checks_inflight = 0
  325. local dmarc_domain_policy = {}
  326. local dmarc_tld_policy = {}
  327. local function process_dmarc_policy(policy, final)
  328. lua_util.debugm(N, task, "validate DMARC policy (final=%s): %s",
  329. true, policy)
  330. if policy.err and policy.symbol then
  331. -- In case of fatal errors or final check for tld, we give up and
  332. -- insert result
  333. if final or policy.fatal then
  334. task:insert_result(policy.symbol, 1.0, policy.err)
  335. maybe_force_action(task, policy.disposition)
  336. return true
  337. end
  338. elseif policy.dmarc_policy then
  339. dmarc_validate_policy(task, policy, hfromdom, dmarc_domain)
  340. return true -- We have a more specific version, use it
  341. end
  342. return false -- Missing record
  343. end
  344. local function gen_dmarc_cb(lookup_domain, is_tld)
  345. local policy_target = dmarc_domain_policy
  346. if is_tld then
  347. policy_target = dmarc_tld_policy
  348. end
  349. return function (_, _, results, err)
  350. dns_checks_inflight = dns_checks_inflight - 1
  351. if not seen_invalid then
  352. policy_target.domain = lookup_domain
  353. if err then
  354. if (err ~= 'requested record is not found' and
  355. err ~= 'no records with this name') then
  356. policy_target.err = lookup_domain .. ' : ' .. err
  357. policy_target.symbol = settings.symbols['dnsfail']
  358. else
  359. policy_target.err = lookup_domain
  360. policy_target.symbol = settings.symbols['na']
  361. end
  362. else
  363. local has_valid_policy = false
  364. for _,rec in ipairs(results) do
  365. local ret,results_or_err = dmarc_common.dmarc_check_record(task, rec, is_tld)
  366. if not ret then
  367. if results_or_err then
  368. -- We have a fatal parsing error, give up
  369. policy_target.err = lookup_domain .. ' : ' .. results_or_err
  370. policy_target.symbol = settings.symbols['badpolicy']
  371. policy_target.fatal = true
  372. seen_invalid = true
  373. end
  374. else
  375. if has_valid_policy then
  376. policy_target.err = lookup_domain .. ' : ' ..
  377. 'Multiple policies defined in DNS'
  378. policy_target.symbol = settings.symbols['badpolicy']
  379. policy_target.fatal = true
  380. seen_invalid = true
  381. end
  382. has_valid_policy = true
  383. for k,v in pairs(results_or_err) do
  384. policy_target[k] = v
  385. end
  386. end
  387. end
  388. if not has_valid_policy and not seen_invalid then
  389. policy_target.err = lookup_domain .. ':' .. ' no valid DMARC record'
  390. policy_target.symbol = settings.symbols['na']
  391. end
  392. end
  393. end
  394. if dns_checks_inflight == 0 then
  395. lua_util.debugm(N, task, "finished DNS queries, validate policies")
  396. -- We have checked both tld and real domain (if different)
  397. if not process_dmarc_policy(dmarc_domain_policy, false) then
  398. -- Try tld policy as well
  399. if not process_dmarc_policy(dmarc_tld_policy, true) then
  400. process_dmarc_policy(dmarc_domain_policy, true)
  401. end
  402. end
  403. end
  404. end
  405. end
  406. local resolve_name = '_dmarc.' .. hfromdom
  407. task:get_resolver():resolve_txt({
  408. task=task,
  409. name = resolve_name,
  410. callback = gen_dmarc_cb(hfromdom, false),
  411. forced = true
  412. })
  413. dns_checks_inflight = dns_checks_inflight + 1
  414. if dmarc_domain ~= hfromdom then
  415. resolve_name = '_dmarc.' .. dmarc_domain
  416. task:get_resolver():resolve_txt({
  417. task=task,
  418. name = resolve_name,
  419. callback = gen_dmarc_cb(dmarc_domain, true),
  420. forced = true
  421. })
  422. dns_checks_inflight = dns_checks_inflight + 1
  423. end
  424. end
  425. local opts = rspamd_config:get_all_opt('dmarc')
  426. settings = lua_util.override_defaults(settings, opts)
  427. settings.auth_and_local_conf = lua_util.config_check_local_or_authed(rspamd_config, N,
  428. false, false)
  429. local lua_maps = require "lua_maps"
  430. lua_maps.fill_config_maps(N, settings, {
  431. no_sampling_domains = {
  432. optional = true,
  433. type = 'map',
  434. description = 'Domains not to apply DMARC sampling to'
  435. },
  436. no_reporting_domains = {
  437. optional = true,
  438. type = 'map',
  439. description = 'Domains not to apply DMARC reporting to'
  440. },
  441. })
  442. if settings.reporting == true then
  443. rspamd_logger.errx(rspamd_config, 'old style dmarc reporting is NO LONGER supported, please read the documentation')
  444. elseif settings.reporting.enabled then
  445. redis_params = lua_redis.parse_redis_server('dmarc', opts)
  446. if not redis_params then
  447. rspamd_logger.errx(rspamd_config, 'cannot parse servers parameter')
  448. else
  449. rspamd_logger.infox(rspamd_config, 'dmarc reporting is enabled')
  450. take_report_id = lua_redis.add_redis_script(take_report_script, redis_params)
  451. end
  452. end
  453. -- Check spf and dkim sections for changed symbols
  454. local function check_mopt(var, m_opts, name)
  455. if m_opts[name] then
  456. settings.symbols[var] = tostring(m_opts[name])
  457. end
  458. end
  459. local spf_opts = rspamd_config:get_all_opt('spf')
  460. if spf_opts then
  461. check_mopt('spf_deny_symbol', spf_opts, 'symbol_fail')
  462. check_mopt('spf_allow_symbol', spf_opts, 'symbol_allow')
  463. check_mopt('spf_softfail_symbol', spf_opts, 'symbol_softfail')
  464. check_mopt('spf_neutral_symbol', spf_opts, 'symbol_neutral')
  465. check_mopt('spf_tempfail_symbol', spf_opts, 'symbol_dnsfail')
  466. check_mopt('spf_na_symbol', spf_opts, 'symbol_na')
  467. end
  468. local dkim_opts = rspamd_config:get_all_opt('dkim')
  469. if dkim_opts then
  470. check_mopt('dkim_deny_symbol', dkim_opts, 'symbol_reject')
  471. check_mopt('dkim_allow_symbol', dkim_opts, 'symbol_allow')
  472. check_mopt('dkim_tempfail_symbol', dkim_opts, 'symbol_tempfail')
  473. check_mopt('dkim_na_symbol', dkim_opts, 'symbol_na')
  474. end
  475. local id = rspamd_config:register_symbol({
  476. name = 'DMARC_CHECK',
  477. type = 'callback',
  478. callback = dmarc_callback
  479. })
  480. rspamd_config:register_symbol({
  481. name = 'DMARC_CALLBACK', -- compatibility symbol
  482. type = 'virtual,skip',
  483. parent = id,
  484. })
  485. rspamd_config:register_symbol({
  486. name = settings.symbols['allow'],
  487. parent = id,
  488. group = 'policies',
  489. groups = {'dmarc'},
  490. type = 'virtual'
  491. })
  492. rspamd_config:register_symbol({
  493. name = settings.symbols['reject'],
  494. parent = id,
  495. group = 'policies',
  496. groups = {'dmarc'},
  497. type = 'virtual'
  498. })
  499. rspamd_config:register_symbol({
  500. name = settings.symbols['quarantine'],
  501. parent = id,
  502. group = 'policies',
  503. groups = {'dmarc'},
  504. type = 'virtual'
  505. })
  506. rspamd_config:register_symbol({
  507. name = settings.symbols['softfail'],
  508. parent = id,
  509. group = 'policies',
  510. groups = {'dmarc'},
  511. type = 'virtual'
  512. })
  513. rspamd_config:register_symbol({
  514. name = settings.symbols['dnsfail'],
  515. parent = id,
  516. group = 'policies',
  517. groups = {'dmarc'},
  518. type = 'virtual'
  519. })
  520. rspamd_config:register_symbol({
  521. name = settings.symbols['badpolicy'],
  522. parent = id,
  523. group = 'policies',
  524. groups = {'dmarc'},
  525. type = 'virtual'
  526. })
  527. rspamd_config:register_symbol({
  528. name = settings.symbols['na'],
  529. parent = id,
  530. group = 'policies',
  531. groups = {'dmarc'},
  532. type = 'virtual'
  533. })
  534. rspamd_config:register_dependency('DMARC_CHECK', settings.symbols['spf_allow_symbol'])
  535. rspamd_config:register_dependency('DMARC_CHECK', settings.symbols['dkim_allow_symbol'])
  536. -- DMARC munging support
  537. if settings.munging then
  538. local lua_maps_expressions = require "lua_maps_expressions"
  539. local munging_defaults = {
  540. reply_goes_to_list = false,
  541. mitigate_allow_only = true, -- perform munging based on DMARC_POLICY_ALLOW only
  542. mitigate_strict_only = false, -- perform mugning merely for reject/quarantine policies
  543. munge_from = true, -- replace from with something like <orig name> via <rcpt user>
  544. list_map = nil, -- map of maillist domains
  545. munge_map_condition = nil, -- maps expression to enable munging
  546. }
  547. local munging_opts = lua_util.override_defaults(munging_defaults, settings.munging)
  548. if not munging_opts.list_map then
  549. rspamd_logger.errx(rspamd_config, 'cannot enable DMARC munging with no list_map parameter')
  550. return
  551. end
  552. munging_opts.list_map = lua_maps.map_add_from_ucl(munging_opts.list_map,
  553. 'set', 'DMARC munging map of the recipients addresses to munge')
  554. if not munging_opts.list_map then
  555. rspamd_logger.errx(rspamd_config, 'cannot enable DMARC munging with invalid list_map (invalid map)')
  556. return
  557. end
  558. if munging_opts.munge_map_condition then
  559. munging_opts.munge_map_condition = lua_maps_expressions.create(rspamd_config,
  560. munging_opts.munge_map_condition, N)
  561. end
  562. rspamd_config:register_symbol({
  563. name = 'DMARC_MUNGED',
  564. type = 'normal',
  565. flags = 'nostat',
  566. score = 0,
  567. group = 'policies',
  568. groups = {'dmarc'},
  569. callback = dmarc_common.gen_munging_callback(munging_opts, settings)
  570. })
  571. rspamd_config:register_dependency('DMARC_MUNGED', 'DMARC_CHECK')
  572. -- To avoid dkim signing issues
  573. rspamd_config:register_dependency('DKIM_SIGNED', 'DMARC_MUNGED')
  574. rspamd_config:register_dependency('ARC_SIGNED', 'DMARC_MUNGED')
  575. rspamd_logger.infox(rspamd_config, 'enabled DMARC munging')
  576. end