]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
Refactor DMARC reporting to use helper functions and async maps 5722/head
authorCursor Agent <cursoragent@cursor.com>
Sat, 1 Nov 2025 12:58:16 +0000 (12:58 +0000)
committerCursor Agent <cursoragent@cursor.com>
Sat, 1 Nov 2025 12:58:16 +0000 (12:58 +0000)
Co-authored-by: v <v@rspamd.com>
src/plugins/lua/dmarc.lua

index cc1a67661c1a31d4e47a153fbea339722313f3f4..da05d13bad9438c4bb4d4c3ac01338a5fb16dffe 100644 (file)
@@ -263,94 +263,188 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
   end
 
   if policy.rua and redis_params and settings.reporting.enabled then
-    if settings.reporting.only_domains then
-      if not (settings.reporting.only_domains:get_key(policy.domain) or
-            settings.reporting.only_domains:get_key(rspamd_util.get_tld(policy.domain))) then
-        rspamd_logger.info(task, 'DMARC reporting suppressed for sender domain %s', policy.domain)
-        return
+    -- Helper function to perform the actual report generation
+    local function generate_dmarc_report()
+      local function dmarc_report_cb(err)
+        if not err then
+          rspamd_logger.infox(task, 'dmarc report saved for %s (rua = %s)',
+            hdrfromdom, policy.rua)
+        else
+          rspamd_logger.errx(task, 'dmarc report is not saved for %s: %s',
+            hdrfromdom, err)
+        end
       end
-    end
-    if settings.reporting.exclude_domains then
-      if settings.reporting.exclude_domains:get_key(policy.domain) or
-          settings.reporting.exclude_domains:get_key(rspamd_util.get_tld(policy.domain)) then
-        rspamd_logger.info(task, 'DMARC reporting suppressed for sender domain %s', policy.domain)
-        return
+
+      local spf_result
+      if spf_ok then
+        spf_result = 'pass'
+      elseif spf_tmpfail then
+        spf_result = 'temperror'
+      else
+        if task:has_symbol(settings.symbols.spf_deny_symbol) then
+          spf_result = 'fail'
+        elseif task:has_symbol(settings.symbols.spf_softfail_symbol) then
+          spf_result = 'softfail'
+        elseif task:has_symbol(settings.symbols.spf_neutral_symbol) then
+          spf_result = 'neutral'
+        elseif task:has_symbol(settings.symbols.spf_permfail_symbol) then
+          spf_result = 'permerror'
+        else
+          spf_result = 'none'
+        end
       end
-    end
-    if settings.reporting.exclude_recipients then
-      local rcpt = task:get_principal_recipient()
-      if rcpt and settings.reporting.exclude_recipients:get_key(rcpt) then
-        rspamd_logger.info(task, 'DMARC reporting suppressed for recipient %s', rcpt)
-        return
+
+      -- Prepare and send redis report element
+      local period = os.date('%Y%m%d',
+        task:get_date({ format = 'connect', gmt = false }))
+
+      -- Dmarc domain key must include dmarc domain, rua and period
+      local dmarc_domain_key = table.concat(
+        { settings.reporting.redis_keys.report_prefix, policy.domain, policy.rua, period },
+        settings.reporting.redis_keys.join_char)
+      local report_data = dmarc_common.dmarc_report(task, settings, {
+        spf_ok = spf_ok and 'pass' or 'fail',
+        dkim_ok = dkim_ok and 'pass' or 'fail',
+        disposition = (disposition == "softfail") and "none" or disposition,
+        sampled_out = sampled_out,
+        domain = hdrfromdom,
+        spf_domain = spf_domain,
+        dkim_results = dkim_results,
+        spf_result = spf_result
+      })
+
+      local idx_key = table.concat({ settings.reporting.redis_keys.index_prefix, period },
+        settings.reporting.redis_keys.join_char)
+
+      if report_data then
+        lua_redis.exec_redis_script(take_report_id,
+          { task = task, is_write = true },
+          dmarc_report_cb,
+          { idx_key, dmarc_domain_key,
+            tostring(settings.reporting.max_entries), tostring(settings.reporting.keys_expire) },
+          { hdrfromdom, report_data })
       end
     end
-    if policy.rua:match("^mailto:") and settings.reporting.exclude_rua_addresses then
-      local rua = policy.rua:gsub("^mailto:", "")
-      if settings.reporting.exclude_rua_addresses:get_key(rua) then
-        rspamd_logger.info(task, 'DMARC reporting suppressed for rua recipient %s', rua)
+
+    -- Helper function to check a map with support for both sync and external maps
+    local function check_map(map_obj, key, continue_cb, suppress_reason)
+      if not map_obj then
+        -- No map configured, continue
+        continue_cb()
         return
       end
-    end
 
-    local function dmarc_report_cb(err)
-      if not err then
-        rspamd_logger.infox(task, 'dmarc report saved for %s (rua = %s)',
-          hdrfromdom, policy.rua)
+      if map_obj.__external then
+        -- External map, use async callback
+        map_obj:get_key(key, function(found, _, _, _)
+          if found then
+            rspamd_logger.infox(task, 'DMARC reporting suppressed: %s', suppress_reason)
+          else
+            continue_cb()
+          end
+        end, task)
       else
-        rspamd_logger.errx(task, 'dmarc report is not saved for %s: %s',
-          hdrfromdom, err)
+        -- Regular map, synchronous check
+        if map_obj:get_key(key) then
+          rspamd_logger.infox(task, 'DMARC reporting suppressed: %s', suppress_reason)
+        else
+          continue_cb()
+        end
       end
     end
 
-    local spf_result
-    if spf_ok then
-      spf_result = 'pass'
-    elseif spf_tmpfail then
-      spf_result = 'temperror'
-    else
-      if task:has_symbol(settings.symbols.spf_deny_symbol) then
-        spf_result = 'fail'
-      elseif task:has_symbol(settings.symbols.spf_softfail_symbol) then
-        spf_result = 'softfail'
-      elseif task:has_symbol(settings.symbols.spf_neutral_symbol) then
-        spf_result = 'neutral'
-      elseif task:has_symbol(settings.symbols.spf_permfail_symbol) then
-        spf_result = 'permerror'
+    -- Forward declarations for chain of exclusion checks
+    local check_only_domains, check_exclude_domains, check_exclude_recipients, check_exclude_rua
+
+    -- Chain exclusion checks together
+    check_only_domains = function()
+      if settings.reporting.only_domains then
+        if settings.reporting.only_domains.__external then
+          -- Check both domain and TLD for external maps
+          settings.reporting.only_domains:get_key(policy.domain, function(found1, _, _, _)
+            if found1 then
+              check_exclude_domains()
+            else
+              settings.reporting.only_domains:get_key(rspamd_util.get_tld(policy.domain), function(found2, _, _, _)
+                if found2 then
+                  check_exclude_domains()
+                else
+                  rspamd_logger.infox(task, 'DMARC reporting suppressed for sender domain %s (not in only_domains)', policy.domain)
+                end
+              end, task)
+            end
+          end, task)
+        else
+          -- Synchronous check for regular maps
+          if settings.reporting.only_domains:get_key(policy.domain) or
+              settings.reporting.only_domains:get_key(rspamd_util.get_tld(policy.domain)) then
+            check_exclude_domains()
+          else
+            rspamd_logger.infox(task, 'DMARC reporting suppressed for sender domain %s (not in only_domains)', policy.domain)
+          end
+        end
       else
-        spf_result = 'none'
+        check_exclude_domains()
       end
     end
 
-    -- Prepare and send redis report element
-    local period = os.date('%Y%m%d',
-      task:get_date({ format = 'connect', gmt = false }))
-
-    -- Dmarc domain key must include dmarc domain, rua and period
-    local dmarc_domain_key = table.concat(
-      { settings.reporting.redis_keys.report_prefix, policy.domain, policy.rua, period },
-      settings.reporting.redis_keys.join_char)
-    local report_data = dmarc_common.dmarc_report(task, settings, {
-      spf_ok = spf_ok and 'pass' or 'fail',
-      dkim_ok = dkim_ok and 'pass' or 'fail',
-      disposition = (disposition == "softfail") and "none" or disposition,
-      sampled_out = sampled_out,
-      domain = hdrfromdom,
-      spf_domain = spf_domain,
-      dkim_results = dkim_results,
-      spf_result = spf_result
-    })
+    check_exclude_domains = function()
+      if settings.reporting.exclude_domains then
+        if settings.reporting.exclude_domains.__external then
+          -- Check both domain and TLD for external maps
+          settings.reporting.exclude_domains:get_key(policy.domain, function(found1, _, _, _)
+            if found1 then
+              rspamd_logger.infox(task, 'DMARC reporting suppressed for sender domain %s', policy.domain)
+            else
+              settings.reporting.exclude_domains:get_key(rspamd_util.get_tld(policy.domain), function(found2, _, _, _)
+                if found2 then
+                  rspamd_logger.infox(task, 'DMARC reporting suppressed for sender domain %s', policy.domain)
+                else
+                  check_exclude_recipients()
+                end
+              end, task)
+            end
+          end, task)
+        else
+          -- Synchronous check for regular maps
+          if settings.reporting.exclude_domains:get_key(policy.domain) or
+              settings.reporting.exclude_domains:get_key(rspamd_util.get_tld(policy.domain)) then
+            rspamd_logger.infox(task, 'DMARC reporting suppressed for sender domain %s', policy.domain)
+          else
+            check_exclude_recipients()
+          end
+        end
+      else
+        check_exclude_recipients()
+      end
+    end
 
-    local idx_key = table.concat({ settings.reporting.redis_keys.index_prefix, period },
-      settings.reporting.redis_keys.join_char)
+    check_exclude_recipients = function()
+      if settings.reporting.exclude_recipients then
+        local rcpt = task:get_principal_recipient()
+        if rcpt then
+          check_map(settings.reporting.exclude_recipients, rcpt, check_exclude_rua,
+            string.format('recipient %s', rcpt))
+        else
+          check_exclude_rua()
+        end
+      else
+        check_exclude_rua()
+      end
+    end
 
-    if report_data then
-      lua_redis.exec_redis_script(take_report_id,
-        { task = task, is_write = true },
-        dmarc_report_cb,
-        { idx_key, dmarc_domain_key,
-          tostring(settings.reporting.max_entries), tostring(settings.reporting.keys_expire) },
-        { hdrfromdom, report_data })
+    check_exclude_rua = function()
+      if policy.rua:match("^mailto:") and settings.reporting.exclude_rua_addresses then
+        local rua = policy.rua:gsub("^mailto:", "")
+        check_map(settings.reporting.exclude_rua_addresses, rua, generate_dmarc_report,
+          string.format('rua recipient %s', rua))
+      else
+        generate_dmarc_report()
+      end
     end
+
+    -- Start the exclusion check chain
+    check_only_domains()
   end
 end