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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  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.reporting.exclude_domains then
  232. if settings.reporting.exclude_domains:get_key(policy.domain) or
  233. settings.reporting.exclude_domains:get_key(rspamd_util.get_tld(policy.domain)) then
  234. rspamd_logger.info(task, 'DMARC reporting suppressed for sender domain %s', policy.domain)
  235. return
  236. end
  237. end
  238. if settings.reporting.exclude_recipients then
  239. local rcpt = task:get_principal_recipient()
  240. if rcpt and settings.reporting.exclude_recipients:get_key(rcpt) then
  241. rspamd_logger.info(task, 'DMARC reporting suppressed for recipient %s', rcpt)
  242. return
  243. end
  244. end
  245. local function dmarc_report_cb(err)
  246. if not err then
  247. rspamd_logger.infox(task, 'dmarc report saved for %s (rua = %s)',
  248. hdrfromdom, policy.rua)
  249. else
  250. rspamd_logger.errx(task, 'dmarc report is not saved for %s: %s',
  251. hdrfromdom, err)
  252. end
  253. end
  254. local spf_result
  255. if spf_ok then
  256. spf_result = 'pass'
  257. elseif spf_tmpfail then
  258. spf_result = 'temperror'
  259. else
  260. if task:has_symbol(settings.symbols.spf_deny_symbol) then
  261. spf_result = 'fail'
  262. elseif task:has_symbol(settings.symbols.spf_softfail_symbol) then
  263. spf_result = 'softfail'
  264. elseif task:has_symbol(settings.symbols.spf_neutral_symbol) then
  265. spf_result = 'neutral'
  266. elseif task:has_symbol(settings.symbols.spf_permfail_symbol) then
  267. spf_result = 'permerror'
  268. else
  269. spf_result = 'none'
  270. end
  271. end
  272. -- Prepare and send redis report element
  273. local period = os.date('%Y%m%d',
  274. task:get_date({ format = 'connect', gmt = false }))
  275. -- Dmarc domain key must include dmarc domain, rua and period
  276. local dmarc_domain_key = table.concat(
  277. { settings.reporting.redis_keys.report_prefix, policy.domain, policy.rua, period },
  278. settings.reporting.redis_keys.join_char)
  279. local report_data = dmarc_common.dmarc_report(task, settings, {
  280. spf_ok = spf_ok and 'pass' or 'fail',
  281. dkim_ok = dkim_ok and 'pass' or 'fail',
  282. disposition = (disposition == "softfail") and "none" or disposition,
  283. sampled_out = sampled_out,
  284. domain = hdrfromdom,
  285. spf_domain = spf_domain,
  286. dkim_results = dkim_results,
  287. spf_result = spf_result
  288. })
  289. local idx_key = table.concat({ settings.reporting.redis_keys.index_prefix, period },
  290. settings.reporting.redis_keys.join_char)
  291. if report_data then
  292. lua_redis.exec_redis_script(take_report_id,
  293. { task = task, is_write = true },
  294. dmarc_report_cb,
  295. { idx_key, dmarc_domain_key,
  296. tostring(settings.reporting.max_entries), tostring(settings.reporting.keys_expire) },
  297. { hdrfromdom, report_data })
  298. end
  299. end
  300. end
  301. local function dmarc_callback(task)
  302. local from = task:get_from(2)
  303. local hfromdom = ((from or E)[1] or E).domain
  304. local dmarc_domain
  305. local ip_addr = task:get_ip()
  306. local dmarc_checks = task:get_mempool():get_variable('dmarc_checks', 'double') or 0
  307. local seen_invalid = false
  308. if dmarc_checks ~= 2 then
  309. rspamd_logger.infox(task, "skip DMARC checks as either SPF or DKIM were not checked")
  310. return
  311. end
  312. if lua_util.is_skip_local_or_authed(task, settings.auth_and_local_conf, ip_addr) then
  313. rspamd_logger.infox(task, "skip DMARC checks for local networks and authorized users")
  314. return
  315. end
  316. -- Do some initial sanity checks, detect tld domain if different
  317. if hfromdom and hfromdom ~= '' and not (from or E)[2] then
  318. -- Lowercase domain as per #3940
  319. hfromdom = hfromdom:lower()
  320. dmarc_domain = rspamd_util.get_tld(hfromdom)
  321. elseif (from or E)[2] then
  322. task:insert_result(settings.symbols['na'], 1.0, 'Duplicate From header')
  323. return maybe_force_action(task, 'na')
  324. elseif (from or E)[1] then
  325. task:insert_result(settings.symbols['na'], 1.0, 'No domain in From header')
  326. return maybe_force_action(task, 'na')
  327. else
  328. task:insert_result(settings.symbols['na'], 1.0, 'No From header')
  329. return maybe_force_action(task, 'na')
  330. end
  331. local dns_checks_inflight = 0
  332. local dmarc_domain_policy = {}
  333. local dmarc_tld_policy = {}
  334. local function process_dmarc_policy(policy, final)
  335. lua_util.debugm(N, task, "validate DMARC policy (final=%s): %s",
  336. true, policy)
  337. if policy.err and policy.symbol then
  338. -- In case of fatal errors or final check for tld, we give up and
  339. -- insert result
  340. if final or policy.fatal then
  341. task:insert_result(policy.symbol, 1.0, policy.err)
  342. maybe_force_action(task, policy.disposition)
  343. return true
  344. end
  345. elseif policy.dmarc_policy then
  346. dmarc_validate_policy(task, policy, hfromdom, dmarc_domain)
  347. return true -- We have a more specific version, use it
  348. end
  349. return false -- Missing record
  350. end
  351. local function gen_dmarc_cb(lookup_domain, is_tld)
  352. local policy_target = dmarc_domain_policy
  353. if is_tld then
  354. policy_target = dmarc_tld_policy
  355. end
  356. return function(_, _, results, err)
  357. dns_checks_inflight = dns_checks_inflight - 1
  358. if not seen_invalid then
  359. policy_target.domain = lookup_domain
  360. if err then
  361. if (err ~= 'requested record is not found' and
  362. err ~= 'no records with this name') then
  363. policy_target.err = lookup_domain .. ' : ' .. err
  364. policy_target.symbol = settings.symbols['dnsfail']
  365. else
  366. policy_target.err = lookup_domain
  367. policy_target.symbol = settings.symbols['na']
  368. end
  369. else
  370. local has_valid_policy = false
  371. for _, rec in ipairs(results) do
  372. local ret, results_or_err = dmarc_common.dmarc_check_record(task, rec, is_tld)
  373. if not ret then
  374. if results_or_err then
  375. -- We have a fatal parsing error, give up
  376. policy_target.err = lookup_domain .. ' : ' .. results_or_err
  377. policy_target.symbol = settings.symbols['badpolicy']
  378. policy_target.fatal = true
  379. seen_invalid = true
  380. end
  381. else
  382. if has_valid_policy then
  383. policy_target.err = lookup_domain .. ' : ' ..
  384. 'Multiple policies defined in DNS'
  385. policy_target.symbol = settings.symbols['badpolicy']
  386. policy_target.fatal = true
  387. seen_invalid = true
  388. end
  389. has_valid_policy = true
  390. for k, v in pairs(results_or_err) do
  391. policy_target[k] = v
  392. end
  393. end
  394. end
  395. if not has_valid_policy and not seen_invalid then
  396. policy_target.err = lookup_domain .. ':' .. ' no valid DMARC record'
  397. policy_target.symbol = settings.symbols['na']
  398. end
  399. end
  400. end
  401. if dns_checks_inflight == 0 then
  402. lua_util.debugm(N, task, "finished DNS queries, validate policies")
  403. -- We have checked both tld and real domain (if different)
  404. if not process_dmarc_policy(dmarc_domain_policy, false) then
  405. -- Try tld policy as well
  406. if not process_dmarc_policy(dmarc_tld_policy, true) then
  407. process_dmarc_policy(dmarc_domain_policy, true)
  408. end
  409. end
  410. end
  411. end
  412. end
  413. local resolve_name = '_dmarc.' .. hfromdom
  414. task:get_resolver():resolve_txt({
  415. task = task,
  416. name = resolve_name,
  417. callback = gen_dmarc_cb(hfromdom, false),
  418. forced = true
  419. })
  420. dns_checks_inflight = dns_checks_inflight + 1
  421. if dmarc_domain ~= hfromdom then
  422. resolve_name = '_dmarc.' .. dmarc_domain
  423. task:get_resolver():resolve_txt({
  424. task = task,
  425. name = resolve_name,
  426. callback = gen_dmarc_cb(dmarc_domain, true),
  427. forced = true
  428. })
  429. dns_checks_inflight = dns_checks_inflight + 1
  430. end
  431. end
  432. local opts = rspamd_config:get_all_opt('dmarc')
  433. settings = lua_util.override_defaults(settings, opts)
  434. settings.auth_and_local_conf = lua_util.config_check_local_or_authed(rspamd_config, N,
  435. false, false)
  436. -- Legacy...
  437. if settings.reporting and not settings.reporting.exclude_domains and settings.no_reporting_domains then
  438. settings.reporting.exclude_domains = settings.no_reporting_domains
  439. end
  440. local lua_maps = require "lua_maps"
  441. lua_maps.fill_config_maps(N, settings, {
  442. no_sampling_domains = {
  443. optional = true,
  444. type = 'map',
  445. description = 'Domains not to apply DMARC sampling to'
  446. },
  447. })
  448. if type(settings.reporting) == 'table' then
  449. lua_maps.fill_config_maps(N, settings.reporting, {
  450. exclude_domains = {
  451. optional = true,
  452. type = 'map',
  453. description = 'Domains not to store DMARC reports about'
  454. },
  455. exclude_recipients = {
  456. optional = true,
  457. type = 'map',
  458. description = 'Recipients not to store DMARC reports for'
  459. },
  460. })
  461. end
  462. if settings.reporting == true then
  463. rspamd_logger.errx(rspamd_config, 'old style dmarc reporting is NO LONGER supported, please read the documentation')
  464. elseif settings.reporting.enabled then
  465. redis_params = lua_redis.parse_redis_server('dmarc', opts)
  466. if not redis_params then
  467. rspamd_logger.errx(rspamd_config, 'cannot parse servers parameter')
  468. else
  469. rspamd_logger.infox(rspamd_config, 'dmarc reporting is enabled')
  470. take_report_id = lua_redis.add_redis_script(take_report_script, redis_params)
  471. end
  472. end
  473. -- Check spf and dkim sections for changed symbols
  474. local function check_mopt(var, m_opts, name)
  475. if m_opts[name] then
  476. settings.symbols[var] = tostring(m_opts[name])
  477. end
  478. end
  479. local spf_opts = rspamd_config:get_all_opt('spf')
  480. if spf_opts then
  481. check_mopt('spf_deny_symbol', spf_opts, 'symbol_fail')
  482. check_mopt('spf_allow_symbol', spf_opts, 'symbol_allow')
  483. check_mopt('spf_softfail_symbol', spf_opts, 'symbol_softfail')
  484. check_mopt('spf_neutral_symbol', spf_opts, 'symbol_neutral')
  485. check_mopt('spf_tempfail_symbol', spf_opts, 'symbol_dnsfail')
  486. check_mopt('spf_na_symbol', spf_opts, 'symbol_na')
  487. end
  488. local dkim_opts = rspamd_config:get_all_opt('dkim')
  489. if dkim_opts then
  490. check_mopt('dkim_deny_symbol', dkim_opts, 'symbol_reject')
  491. check_mopt('dkim_allow_symbol', dkim_opts, 'symbol_allow')
  492. check_mopt('dkim_tempfail_symbol', dkim_opts, 'symbol_tempfail')
  493. check_mopt('dkim_na_symbol', dkim_opts, 'symbol_na')
  494. end
  495. local id = rspamd_config:register_symbol({
  496. name = 'DMARC_CHECK',
  497. type = 'callback',
  498. callback = dmarc_callback
  499. })
  500. rspamd_config:register_symbol({
  501. name = 'DMARC_CALLBACK', -- compatibility symbol
  502. type = 'virtual,skip',
  503. parent = id,
  504. })
  505. rspamd_config:register_symbol({
  506. name = settings.symbols['allow'],
  507. parent = id,
  508. group = 'policies',
  509. groups = { 'dmarc' },
  510. type = 'virtual'
  511. })
  512. rspamd_config:register_symbol({
  513. name = settings.symbols['reject'],
  514. parent = id,
  515. group = 'policies',
  516. groups = { 'dmarc' },
  517. type = 'virtual'
  518. })
  519. rspamd_config:register_symbol({
  520. name = settings.symbols['quarantine'],
  521. parent = id,
  522. group = 'policies',
  523. groups = { 'dmarc' },
  524. type = 'virtual'
  525. })
  526. rspamd_config:register_symbol({
  527. name = settings.symbols['softfail'],
  528. parent = id,
  529. group = 'policies',
  530. groups = { 'dmarc' },
  531. type = 'virtual'
  532. })
  533. rspamd_config:register_symbol({
  534. name = settings.symbols['dnsfail'],
  535. parent = id,
  536. group = 'policies',
  537. groups = { 'dmarc' },
  538. type = 'virtual'
  539. })
  540. rspamd_config:register_symbol({
  541. name = settings.symbols['badpolicy'],
  542. parent = id,
  543. group = 'policies',
  544. groups = { 'dmarc' },
  545. type = 'virtual'
  546. })
  547. rspamd_config:register_symbol({
  548. name = settings.symbols['na'],
  549. parent = id,
  550. group = 'policies',
  551. groups = { 'dmarc' },
  552. type = 'virtual'
  553. })
  554. rspamd_config:register_dependency('DMARC_CHECK', settings.symbols['spf_allow_symbol'])
  555. rspamd_config:register_dependency('DMARC_CHECK', settings.symbols['dkim_allow_symbol'])
  556. -- DMARC munging support
  557. if settings.munging then
  558. local lua_maps_expressions = require "lua_maps_expressions"
  559. local munging_defaults = {
  560. reply_goes_to_list = false,
  561. mitigate_allow_only = true, -- perform munging based on DMARC_POLICY_ALLOW only
  562. mitigate_strict_only = false, -- perform mugning merely for reject/quarantine policies
  563. munge_from = true, -- replace from with something like <orig name> via <rcpt user>
  564. list_map = nil, -- map of maillist domains
  565. munge_map_condition = nil, -- maps expression to enable munging
  566. }
  567. local munging_opts = lua_util.override_defaults(munging_defaults, settings.munging)
  568. if not munging_opts.list_map then
  569. rspamd_logger.errx(rspamd_config, 'cannot enable DMARC munging with no list_map parameter')
  570. return
  571. end
  572. munging_opts.list_map = lua_maps.map_add_from_ucl(munging_opts.list_map,
  573. 'set', 'DMARC munging map of the recipients addresses to munge')
  574. if not munging_opts.list_map then
  575. rspamd_logger.errx(rspamd_config, 'cannot enable DMARC munging with invalid list_map (invalid map)')
  576. return
  577. end
  578. if munging_opts.munge_map_condition then
  579. munging_opts.munge_map_condition = lua_maps_expressions.create(rspamd_config,
  580. munging_opts.munge_map_condition, N)
  581. end
  582. rspamd_config:register_symbol({
  583. name = 'DMARC_MUNGED',
  584. type = 'normal',
  585. flags = 'nostat',
  586. score = 0,
  587. group = 'policies',
  588. groups = { 'dmarc' },
  589. callback = dmarc_common.gen_munging_callback(munging_opts, settings),
  590. augmentations = { lua_util.dns_timeout_augmentation(rspamd_config) },
  591. })
  592. rspamd_config:register_dependency('DMARC_MUNGED', 'DMARC_CHECK')
  593. -- To avoid dkim signing issues
  594. rspamd_config:register_dependency('DKIM_SIGNED', 'DMARC_MUNGED')
  595. rspamd_config:register_dependency('ARC_SIGNED', 'DMARC_MUNGED')
  596. rspamd_logger.infox(rspamd_config, 'enabled DMARC munging')
  597. end