commit b75b02a: [Rework] Reorganize dmarc plugin and remove unsupported reporting code

Vsevolod Stakhov vsevolod at highsecure.ru
Thu Jul 29 14:56:05 UTC 2021


Author: Vsevolod Stakhov
Date: 2021-07-29 15:54:25 +0100
URL: https://github.com/rspamd/rspamd/commit/b75b02a67bad7cba5635ec93123814d91baf1f53 (HEAD -> master)

[Rework] Reorganize dmarc plugin and remove unsupported reporting code

---
 src/plugins/lua/dmarc.lua | 894 ++++++----------------------------------------
 1 file changed, 113 insertions(+), 781 deletions(-)

diff --git a/src/plugins/lua/dmarc.lua b/src/plugins/lua/dmarc.lua
index ef2c89881..e38ab822b 100644
--- a/src/plugins/lua/dmarc.lua
+++ b/src/plugins/lua/dmarc.lua
@@ -18,135 +18,58 @@ limitations under the License.
 -- Dmarc policy filter
 
 local rspamd_logger = require "rspamd_logger"
-local mempool = require "rspamd_mempool"
-local rspamd_url = require "rspamd_url"
 local rspamd_util = require "rspamd_util"
 local rspamd_redis = require "lua_redis"
 local lua_util = require "lua_util"
-local auth_and_local_conf
 
 if confighelp then
   return
 end
 
 local N = 'dmarc'
-local no_sampling_domains
-local no_reporting_domains
-local statefile = string.format('%s/%s', rspamd_paths['DBDIR'], 'dmarc_reports_last_sent')
-local VAR_NAME = 'dmarc_reports_last_sent'
-local INTERVAL = 86400
-local pool
-
-local report_settings = {
-  helo = 'rspamd',
-  hscan_count = 1000,
-  smtp = '127.0.0.1',
-  smtp_port = 25,
-  retries = 2,
-  from_name = 'Rspamd',
-  msgid_from = 'rspamd',
-}
-
-local report_template = [[From: "{= from_name =}" <{= from_addr =}>
-To: {= rcpt =}
-{%+ if is_string(bcc) %}Bcc: {= bcc =}{%- endif %}
-Subject: Report Domain: {= reporting_domain =}
-	Submitter: {= submitter =}
-	Report-ID: {= report_id =}
-Date: {= report_date =}
-MIME-Version: 1.0
-Message-ID: <{= message_id =}>
-Content-Type: multipart/mixed;
-	boundary="----=_NextPart_000_024E_01CC9B0A.AFE54C00"
-
-This is a multipart message in MIME format.
-
-------=_NextPart_000_024E_01CC9B0A.AFE54C00
-Content-Type: text/plain; charset="us-ascii"
-Content-Transfer-Encoding: 7bit
-
-This is an aggregate report from {= submitter =}.
-
-Report domain: {= reporting_domain =}
-Submitter: {= submitter =}
-Report ID: {= report_id =}
-
-------=_NextPart_000_024E_01CC9B0A.AFE54C00
-Content-Type: application/gzip
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment;
-	filename="{= submitter =}!{= reporting_domain =}!{= report_start =}!{= report_end =}.xml.gz"
-
-]]
-local report_footer = [[
-
-------=_NextPart_000_024E_01CC9B0A.AFE54C00--]]
-
-local symbols = {
-  spf_allow_symbol = 'R_SPF_ALLOW',
-  spf_deny_symbol = 'R_SPF_FAIL',
-  spf_softfail_symbol = 'R_SPF_SOFTFAIL',
-  spf_neutral_symbol = 'R_SPF_NEUTRAL',
-  spf_tempfail_symbol = 'R_SPF_DNSFAIL',
-  spf_permfail_symbol = 'R_SPF_PERMFAIL',
-  spf_na_symbol = 'R_SPF_NA',
-
-  dkim_allow_symbol = 'R_DKIM_ALLOW',
-  dkim_deny_symbol = 'R_DKIM_REJECT',
-  dkim_tempfail_symbol = 'R_DKIM_TEMPFAIL',
-  dkim_na_symbol = 'R_DKIM_NA',
-  dkim_permfail_symbol = 'R_DKIM_PERMFAIL',
-}
 
-local dmarc_symbols = {
-  allow = 'DMARC_POLICY_ALLOW',
-  badpolicy = 'DMARC_BAD_POLICY',
-  dnsfail = 'DMARC_DNSFAIL',
-  na = 'DMARC_NA',
-  reject = 'DMARC_POLICY_REJECT',
-  softfail = 'DMARC_POLICY_SOFTFAIL',
-  quarantine = 'DMARC_POLICY_QUARANTINE',
+local settings = {
+  auth_and_local_conf = false,
+  symbols = {
+    spf_allow_symbol = 'R_SPF_ALLOW',
+    spf_deny_symbol = 'R_SPF_FAIL',
+    spf_softfail_symbol = 'R_SPF_SOFTFAIL',
+    spf_neutral_symbol = 'R_SPF_NEUTRAL',
+    spf_tempfail_symbol = 'R_SPF_DNSFAIL',
+    spf_permfail_symbol = 'R_SPF_PERMFAIL',
+    spf_na_symbol = 'R_SPF_NA',
+
+    dkim_allow_symbol = 'R_DKIM_ALLOW',
+    dkim_deny_symbol = 'R_DKIM_REJECT',
+    dkim_tempfail_symbol = 'R_DKIM_TEMPFAIL',
+    dkim_na_symbol = 'R_DKIM_NA',
+    dkim_permfail_symbol = 'R_DKIM_PERMFAIL',
+
+    -- DMARC symbols
+    allow = 'DMARC_POLICY_ALLOW',
+    badpolicy = 'DMARC_BAD_POLICY',
+    dnsfail = 'DMARC_DNSFAIL',
+    na = 'DMARC_NA',
+    reject = 'DMARC_POLICY_REJECT',
+    softfail = 'DMARC_POLICY_SOFTFAIL',
+    quarantine = 'DMARC_POLICY_QUARANTINE',
+  },
+  no_sampling_domains = nil,
+  no_reporting_domains = nil,
+  reporting = {
+    redis_keys = {
+      index_prefix = 'dmarc_idx',
+      report_prefix = 'dmarc',
+      join_char = ';',
+    },
+    enabled = false,
+    max_entries = 1000,
+    only_domains = nil,
+  },
+  actions = {},
 }
 
-local redis_keys = {
-  index_prefix = 'dmarc_idx',
-  report_prefix = 'dmarc',
-  join_char = ';',
-}
-
-local function gen_xml_grammar()
-  local lpeg = require 'lpeg'
-  local lt = lpeg.P('<') / '<'
-  local gt = lpeg.P('>') / '>'
-  local amp = lpeg.P('&') / '&'
-  local quot = lpeg.P('"') / '"'
-  local apos = lpeg.P("'") / '''
-  local special = lt + gt + amp + quot + apos
-  local grammar = lpeg.Cs((special + 1)^0)
-  return grammar
-end
-
-local xml_grammar = gen_xml_grammar()
-
-local function escape_xml(input)
-  if type(input) == 'string' or type(input) == 'userdata' then
-    return xml_grammar:match(input)
-  else
-    input = tostring(input)
-
-    if input then
-      return xml_grammar:match(input)
-    end
-  end
-
-  return ''
-end
-
--- Default port for redis upstreams
 local redis_params = nil
--- 2 days
-local dmarc_reporting = false
-local dmarc_actions = {}
 
 local E = {}
 
@@ -162,17 +85,6 @@ redis.call('HINCRBY', report_key, report, 1)
 redis.call('EXPIRE', report_key, 172800)
 ]]
 
--- return the timezone offset in seconds, as it was on the time given by ts
--- Eric Feliksik
-local function get_timezone_offset(ts)
-  local utcdate   = os.date("!*t", ts)
-  local localdate = os.date("*t", ts)
-  localdate.isdst = false -- this is the trick
-  return os.difftime(os.time(localdate), os.time(utcdate))
-end
-
-local tz_offset = get_timezone_offset(os.time())
-
 local function gen_dmarc_grammar()
   local lpeg = require "lpeg"
   lpeg.locale(lpeg)
@@ -207,6 +119,8 @@ local function dmarc_key_value_case(elts)
   return result
 end
 
+
+-- Returns a key used to be inserted into dmarc report sample
 local function dmarc_report(task, spf_ok, dkim_ok, disposition,
     sampled_out, hfromdom, spfdom, dres, spf_result)
   local ip = task:get_from_ip()
@@ -230,7 +144,7 @@ end
 
 local function maybe_force_action(task, disposition)
   if disposition then
-    local force_action = dmarc_actions[disposition]
+    local force_action = settings.actions[disposition]
     if force_action then
       -- Set least action
       task:set_pre_result(force_action, 'Action set by DMARC', N, nil, nil, 'least')
@@ -337,7 +251,7 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
     spf_domain = task:get_helo() or ''
   end
 
-  if task:has_symbol(symbols['spf_allow_symbol']) then
+  if task:has_symbol(settings.symbols['spf_allow_symbol']) then
     if policy.strict_spf then
       if rspamd_util.strequal_caseless(spf_domain, hdrfromdom) then
         spf_ok = true
@@ -353,7 +267,7 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
       end
     end
   else
-    if task:has_symbol(symbols['spf_tempfail_symbol']) then
+    if task:has_symbol(settings.symbols['spf_tempfail_symbol']) then
       if policy.strict_spf then
         if rspamd_util.strequal_caseless(spf_domain, hdrfromdom) then
           spf_tmpfail = true
@@ -446,14 +360,14 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
 
   local function handle_dmarc_failure(what, reason_str)
     if not policy.pct or policy.pct == 100 then
-      task:insert_result(dmarc_symbols[what], 1.0,
+      task:insert_result(settings.symbols[what], 1.0,
           policy.domain .. ' : ' .. reason_str, policy.dmarc_policy)
       disposition = what
     else
       local coin = math.random(100)
       if (coin > policy.pct) then
-        if (not no_sampling_domains or
-            not no_sampling_domains:get_key(policy.domain)) then
+        if (not settings.no_sampling_domains or
+            not settings.no_sampling_domains:get_key(policy.domain)) then
 
           if what == 'reject' then
             disposition = 'quarantine'
@@ -461,19 +375,19 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
             disposition = 'softfail'
           end
 
-          task:insert_result(dmarc_symbols[disposition], 1.0,
+          task:insert_result(settings.symbols[disposition], 1.0,
               policy.domain .. ' : ' .. reason_str, policy.dmarc_policy, "sampled_out")
           sampled_out = true
           lua_util.debugm(N, task,
               'changed dmarc policy from %s to %s, sampled out: %s < %s',
               what, disposition, coin, policy.pct)
         else
-          task:insert_result(dmarc_symbols[what], 1.0,
+          task:insert_result(settings.symbols[what], 1.0,
               policy.domain .. ' : ' .. reason_str, policy.dmarc_policy, "local_policy")
           disposition = what
         end
       else
-        task:insert_result(dmarc_symbols[what], 1.0,
+        task:insert_result(settings.symbols[what], 1.0,
             policy.domain .. ' : ' .. reason_str, policy.dmarc_policy)
         disposition = what
       end
@@ -489,7 +403,7 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
     underlying authentication mechanisms passes for an aligned
     identifier.
     ]]--
-    task:insert_result(dmarc_symbols['allow'], 1.0, policy.domain,
+    task:insert_result(settings.symbols['allow'], 1.0, policy.domain,
         policy.dmarc_policy)
   else
     --[[
@@ -501,7 +415,7 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
     therefore cannot apply the advertised DMARC policy.
     ]]--
     if spf_tmpfail or dkim_tmpfail then
-      task:insert_result(dmarc_symbols['dnsfail'], 1.0, policy.domain..
+      task:insert_result(settings.symbols['dnsfail'], 1.0, policy.domain..
           ' : ' .. 'SPF/DKIM temp error', policy.dmarc_policy)
     else
       -- We can now check the failed policy and maybe send report data elt
@@ -512,17 +426,17 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
       elseif policy.dmarc_policy == 'reject' then
         handle_dmarc_failure('reject', reason_str)
       else
-        task:insert_result(dmarc_symbols['softfail'], 1.0,
+        task:insert_result(settings.symbols['softfail'], 1.0,
             policy.domain .. ' : ' .. reason_str,
             policy.dmarc_policy)
       end
     end
   end
 
-  if policy.rua and redis_params and dmarc_reporting then
-    if no_reporting_domains then
-      if no_reporting_domains:get_key(policy.domain) or
-          no_reporting_domains:get_key(rspamd_util.get_tld(policy.domain)) then
+  if policy.rua and redis_params and settings.reporting.enabled then
+    if settings.no_reporting_domains then
+      if settings.no_reporting_domains:get_key(policy.domain) or
+          settings.no_reporting_domains:get_key(rspamd_util.get_tld(policy.domain)) then
         rspamd_logger.infox(task, 'DMARC reporting suppressed for %1', policy.domain)
         return
       end
@@ -544,13 +458,13 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
     elseif spf_tmpfail then
       spf_result = 'temperror'
     else
-      if task:has_symbol(symbols.spf_deny_symbol) then
+      if task:has_symbol(settings.symbols.spf_deny_symbol) then
         spf_result = 'fail'
-      elseif task:has_symbol(symbols.spf_softfail_symbol) then
+      elseif task:has_symbol(settings.symbols.spf_softfail_symbol) then
         spf_result = 'softfail'
-      elseif task:has_symbol(symbols.spf_neutral_symbol) then
+      elseif task:has_symbol(settings.symbols.spf_neutral_symbol) then
         spf_result = 'neutral'
-      elseif task:has_symbol(symbols.spf_permfail_symbol) then
+      elseif task:has_symbol(settings.symbols.spf_permfail_symbol) then
         spf_result = 'permerror'
       else
         spf_result = 'none'
@@ -561,7 +475,8 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
     local period = os.date('!%Y%m%d',
         task:get_date({format = 'connect', gmt = true}))
     local dmarc_domain_key = table.concat(
-        {redis_keys.report_prefix, hdrfromdom, period}, redis_keys.join_char)
+        {settings.reporting.redis_keys.report_prefix, hdrfromdom, period},
+        settings.reporting.redis_keys.join_char)
     local report_data = dmarc_report(task,
         spf_ok and 'pass' or 'fail',
         dkim_ok and 'pass' or 'fail',
@@ -572,8 +487,8 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld)
         dkim_results,
         spf_result)
 
-    local idx_key = table.concat({redis_keys.index_prefix, period},
-        redis_keys.join_char)
+    local idx_key = table.concat({settings.redis_keys.index_prefix, period},
+        settings.redis_keys.join_char)
 
     if report_data then
       rspamd_redis.exec_redis_script(take_report_id,
@@ -598,7 +513,7 @@ local function dmarc_callback(task)
     return
   end
 
-  if lua_util.is_skip_local_or_authed(task, auth_and_local_conf, ip_addr) then
+  if lua_util.is_skip_local_or_authed(task, settings.auth_and_local_conf, ip_addr) then
     rspamd_logger.infox(task, "skip DMARC checks for local networks and authorized users")
     return
   end
@@ -607,13 +522,13 @@ local function dmarc_callback(task)
   if hfromdom and hfromdom ~= '' and not (from or E)[2] then
     dmarc_domain = rspamd_util.get_tld(hfromdom)
   elseif (from or E)[2] then
-    task:insert_result(dmarc_symbols['na'], 1.0, 'Duplicate From header')
+    task:insert_result(settings.symbols['na'], 1.0, 'Duplicate From header')
     return maybe_force_action(task, 'na')
   elseif (from or E)[1] then
-    task:insert_result(dmarc_symbols['na'], 1.0, 'No domain in From header')
+    task:insert_result(settings.symbols['na'], 1.0, 'No domain in From header')
     return maybe_force_action(task,'na')
   else
-    task:insert_result(dmarc_symbols['na'], 1.0, 'No From header')
+    task:insert_result(settings.symbols['na'], 1.0, 'No From header')
     return maybe_force_action(task,'na')
   end
 
@@ -659,10 +574,10 @@ local function dmarc_callback(task)
           if (err ~= 'requested record is not found' and
               err ~= 'no records with this name') then
             policy_target.err = lookup_domain .. ' : ' .. err
-            policy_target.symbol = dmarc_symbols['dnsfail']
+            policy_target.symbol = settings.symbols['dnsfail']
           else
             policy_target.err = lookup_domain
-            policy_target.symbol = dmarc_symbols['na']
+            policy_target.symbol = settings.symbols['na']
           end
         else
           local has_valid_policy = false
@@ -674,7 +589,7 @@ local function dmarc_callback(task)
               if results_or_err then
                 -- We have a fatal parsing error, give up
                 policy_target.err = lookup_domain .. ' : ' .. results_or_err
-                policy_target.symbol = dmarc_symbols['badpolicy']
+                policy_target.symbol = settings.symbols['badpolicy']
                 policy_target.fatal = true
                 seen_invalid = true
               end
@@ -682,7 +597,7 @@ local function dmarc_callback(task)
               if has_valid_policy then
                 policy_target.err = lookup_domain .. ' : ' ..
                     'Multiple policies defined in DNS'
-                policy_target.symbol = dmarc_symbols['badpolicy']
+                policy_target.symbol = settings.symbols['badpolicy']
                 policy_target.fatal = true
                 seen_invalid = true
               end
@@ -696,7 +611,7 @@ local function dmarc_callback(task)
 
           if not has_valid_policy and not seen_invalid then
             policy_target.err = lookup_domain .. ':' .. ' no valid DMARC record'
-            policy_target.symbol = dmarc_symbols['na']
+            policy_target.symbol = settings.symbols['na']
           end
         end
       end
@@ -740,622 +655,41 @@ end
 
 
 local opts = rspamd_config:get_all_opt('dmarc')
-if not opts or type(opts) ~= 'table' then
-  return
-end
+settings = lua_util.override_defaults(settings, opts)
 
-auth_and_local_conf = lua_util.config_check_local_or_authed(rspamd_config, N,
+settings.auth_and_local_conf = lua_util.config_check_local_or_authed(rspamd_config, N,
     false, false)
 
-no_sampling_domains = rspamd_map_add(N, 'no_sampling_domains', 'map', 'Domains not to apply DMARC sampling to')
-no_reporting_domains = rspamd_map_add(N, 'no_reporting_domains', 'map', 'Domains not to apply DMARC reporting to')
+local lua_maps = require "lua_maps"
+lua_maps.fill_config_maps(N, settings, {
+  no_sampling_domains = {
+    optional = true,
+    type = 'map',
+    description = 'Domains not to apply DMARC sampling to'
+  },
+  no_reporting_domains = {
+    optional = true,
+    type = 'map',
+    description = 'Domains not to apply DMARC reporting to'
+  },
+})
 
-if opts['symbols'] then
-  for k,_ in pairs(dmarc_symbols) do
-    if opts['symbols'][k] then
-      dmarc_symbols[k] = opts['symbols'][k]
-    end
-  end
-end
 
--- Reporting related code --
-
----
---- Converts a reporting entry to an XML format
---- @param data data table
---- @return string with an XML representation
-local function entry_to_xml(data)
-  local buf = {
-    table.concat({
-      '<record><row><source_ip>', data.ip, '</source_ip><count>',
-      data.count, '</count><policy_evaluated><disposition>',
-      data.disposition, '</disposition><dkim>', data.dkim_disposition,
-      '</dkim><spf>', data.spf_disposition, '</spf>'
-    }),
-  }
-  if data.override ~= '' then
-    table.insert(buf, string.format('<reason><type>%s</type></reason>', data.override))
-  end
-  table.insert(buf, table.concat({
-    '</policy_evaluated></row><identifiers><header_from>', data.header_from,
-    '</header_from></identifiers>',
-  }))
-  table.insert(buf, '<auth_results>')
-  if data.dkim_results[1] then
-    for _, d in ipairs(data.dkim_results) do
-      table.insert(buf, table.concat({
-        '<dkim><domain>', d.domain, '</domain><result>',
-        d.result, '</result></dkim>',
-      }))
-    end
-  end
-  table.insert(buf, table.concat({
-    '<spf><domain>', data.spf_domain, '</domain><result>',
-    data.spf_result, '</result></spf></auth_results></record>',
-  }))
-  return table.concat(buf)
-end
-
-if opts['reporting'] == true then
+if settings.reporting == true then
+  rspamd_logger.errx(rspamd_config, 'old style dmarc reporting is NO LONGER supported, please read the documentation')
+elseif settings.reporting.enabled then
   redis_params = rspamd_parse_redis_server('dmarc')
   if not redis_params then
     rspamd_logger.errx(rspamd_config, 'cannot parse servers parameter')
-  elseif not opts['send_reports'] then
-    dmarc_reporting = true
-    take_report_id = rspamd_redis.add_redis_script(take_report_script, redis_params)
   else
-    dmarc_reporting = true
-    if type(opts['report_settings']) == 'table' then
-      for k, v in pairs(opts['report_settings']) do
-        report_settings[k] = v
-      end
-    end
-    for _, e in ipairs({'email', 'domain', 'org_name'}) do
-      if not report_settings[e] then
-        rspamd_logger.errx(rspamd_config, 'Missing required setting: report_settings.%s', e)
-        return
-      end
-    end
     take_report_id = rspamd_redis.add_redis_script(take_report_script, redis_params)
-    rspamd_config:add_on_load(function(cfg, ev_base, worker)
-      if not worker:is_primary_controller() then return end
-
-      pool = mempool.create()
-
-      rspamd_config:register_finish_script(function ()
-        local stamp = pool:get_variable(VAR_NAME, 'double')
-        if not stamp then
-          rspamd_logger.warnx(rspamd_config, 'No last DMARC report information to persist to disk')
-          return
-        end
-        local f, err = io.open(statefile, 'w')
-        if err then
-          rspamd_logger.errx(rspamd_config, 'Unable to write statefile to disk: %s', err)
-          return
-        end
-        assert(f:write(pool:get_variable(VAR_NAME, 'double')))
-        assert(f:close())
-        pool:destroy()
-      end)
-
-      local get_reporting_domain, reporting_domain, report_start,
-            report_end, report_id, want_period, report_key
-      local reporting_addrs = {}
-      local bcc_addrs = {}
-      local domain_policy = {}
-      local to_verify = {}
-      local cursor = 0
-      local function dmarc_report_xml()
-        local entries = {}
-        report_id = string.format('%s.%d.%d',
-            reporting_domain, report_start, report_end)
-        lua_util.debugm(N, rspamd_config, 'new report: %s', report_id)
-        local actions = {
-          push = function(t)
-            local data = t[1]
-            local split = rspamd_str_split(data, ',')
-            local row = {
-              ip = split[1],
-              spf_disposition = split[2],
-              dkim_disposition = split[3],
-              disposition = split[4],
-              override = split[5],
-              header_from = split[6],
-              dkim_results = {},
-              spf_domain = split[11],
-              spf_result = split[12],
-              count = t[2],
-            }
-            if split[7] and split[7] ~= '' then
-              local tmp = rspamd_str_split(split[7], '|')
-              for _, d in ipairs(tmp) do
-                table.insert(row.dkim_results, {domain = d, result = 'pass'})
-              end
-            end
-            if split[8] and split[8] ~= '' then
-              local tmp = rspamd_str_split(split[8], '|')
-              for _, d in ipairs(tmp) do
-                table.insert(row.dkim_results, {domain = d, result = 'fail'})
-              end
-            end
-            if split[9] and split[9] ~= '' then
-              local tmp = rspamd_str_split(split[9], '|')
-              for _, d in ipairs(tmp) do
-                table.insert(row.dkim_results, {domain = d, result = 'temperror'})
-              end
-            end
-            if split[10] and split[10] ~= '' then
-              local tmp = lua_util.str_split(split[10], '|')
-              for _, d in ipairs(tmp) do
-                table.insert(row.dkim_results,
-                    {domain = d, result = 'permerror'})
-              end
-            end
-            table.insert(entries, row)
-          end,
-          -- TODO: please rework this shit
-          header = function()
-              return table.concat({
-                '<?xml version="1.0" encoding="utf-8"?><feedback><report_metadata><org_name>',
-                escape_xml(report_settings.org_name), '</org_name><email>',
-                escape_xml(report_settings.email), '</email><report_id>',
-                report_id, '</report_id><date_range><begin>', report_start,
-                '</begin><end>', report_end, '</end></date_range></report_metadata><policy_published><domain>',
-                reporting_domain, '</domain><adkim>', escape_xml(domain_policy.adkim), '</adkim><aspf>',
-                escape_xml(domain_policy.aspf), '</aspf><p>', escape_xml(domain_policy.p),
-                '</p><sp>', escape_xml(domain_policy.sp), '</sp><pct>',
-                escape_xml(domain_policy.pct),
-                '</pct></policy_published>'
-              })
-          end,
-          footer = function()
-            return [[</feedback>]]
-          end,
-          entries = function()
-            local buf = {}
-            for _, e in pairs(entries) do
-              table.insert(buf, entry_to_xml(e))
-            end
-            return table.concat(buf, '')
-          end,
-        }
-        return function(action, p)
-          local f = actions[action]
-          if not f then error('invalid action: ' .. action) end
-          return f(p)
-        end
-      end
-
-
-      local function send_report_via_email(xmlf, retry)
-        if not retry then retry = 0 end
-
-        local function sendmail_cb(ret, err)
-          if not ret then
-            rspamd_logger.errx(rspamd_config, "Couldn't send mail for %s: %s", err)
-            if retry >= report_settings.retries then
-              rspamd_logger.errx(rspamd_config, "Couldn't send mail for %s: retries exceeded", reporting_domain)
-              return get_reporting_domain()
-            else
-              send_report_via_email(xmlf, retry + 1)
-            end
-          else
-            get_reporting_domain()
-          end
-        end
-
-        -- Format message
-        local list_rcpt = lua_util.keys(reporting_addrs)
-
-        local encoded = rspamd_util.encode_base64(rspamd_util.gzip_compress(
-              table.concat(
-                {xmlf('header'),
-                 xmlf('entries'),
-                 xmlf('footer')})), 73)
-        local addr_string = table.concat(list_rcpt, ', ')
-
-        local bcc_addrs_keys = lua_util.keys(bcc_addrs)
-        local bcc_string
-        if #bcc_addrs_keys > 0 then
-          bcc_string = table.concat(bcc_addrs_keys, ', ')
-        end
-
-        local rhead = lua_util.jinja_template(report_template,
-            {
-              from_name = report_settings.from_name,
-              from_addr = report_settings.email,
-              rcpt = addr_string,
-              bcc = bcc_string,
-              reporting_domain = reporting_domain,
-              submitter = report_settings.domain,
-              report_id = report_id,
-              report_date = rspamd_util.time_to_string(rspamd_util.get_time()),
-              message_id = rspamd_util.random_hex(16) .. '@' .. report_settings.msgid_from,
-              report_start = report_start,
-              report_end = report_end
-            }, true)
-        local message = {
-          (rhead:gsub("\n", "\r\n")),
-          encoded,
-          (report_footer:gsub("\n", "\r\n"))
-        }
-
-        local lua_smtp = require "lua_smtp"
-        lua_smtp.sendmail({
-          ev_base = ev_base,
-          config = rspamd_config,
-          host = report_settings.smtp,
-          port = report_settings.smtp_port,
-          resolver = rspamd_config:get_resolver(),
-          from = report_settings.email,
*** OUTPUT TRUNCATED, 499 LINES SKIPPED ***


More information about the Commits mailing list