HEX
Server: nginx/1.28.0
System: Linux w3c-2 6.8.0-78-generic #78-Ubuntu SMP PREEMPT_DYNAMIC Tue Aug 12 11:34:18 UTC 2025 x86_64
User: inpa_co_1 (1082)
PHP: 8.3.29
Disabled: NONE
Upload Files
File: //usr/share/rspamd/plugins/dkim_signing.lua
--[[
Copyright (c) 2016, Andrew Lewis <[email protected]>
Copyright (c) 2022, Vsevolod Stakhov <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--

local lua_util = require "lua_util"
local rspamd_logger = require "rspamd_logger"
local dkim_sign_tools = require "lua_dkim_tools"
local lua_redis = require "lua_redis"
local lua_mime = require "lua_mime"

if confighelp then
  return
end

local settings = {
  allow_envfrom_empty = true,
  allow_hdrfrom_mismatch = false,
  allow_hdrfrom_mismatch_local = false,
  allow_hdrfrom_mismatch_sign_networks = false,
  allow_hdrfrom_multiple = false,
  allow_username_mismatch = false,
  allow_pubkey_mismatch = true,
  sign_authenticated = true,
  allowed_ids = nil,
  forbidden_ids = nil,
  check_pubkey = false,
  domain = {},
  path = string.format('%s/%s/%s', rspamd_paths['DBDIR'], 'dkim', '$domain.$selector.key'),
  sign_local = true,
  selector = 'dkim',
  symbol = 'DKIM_SIGNED',
  try_fallback = true,
  use_domain = 'header',
  use_esld = true,
  use_redis = false,
  key_prefix = 'dkim_keys', -- default hash name
  use_milter_headers = false, -- use milter headers instead of `dkim_signature`
}

local N = 'dkim_signing'
local redis_params
local sign_func = rspamd_plugins.dkim.sign

local function insert_sign_results(task, ret, hdr, dkim_params)
  if settings.use_milter_headers then
    lua_mime.modify_headers(task, {
      add = {
        ['DKIM-Signature'] = { order = 1, value = hdr },
      }
    })
  end
  if ret then
    task:insert_result(settings.symbol, 1.0, string.format('%s:s=%s',
        dkim_params.domain, dkim_params.selector))
  end
end

local function do_sign(task, p)
  if settings.use_milter_headers then
    p.no_cache = true -- Disable caching in rspamd_mempool
  end
  if settings.check_pubkey then
    local resolve_name = p.selector .. "._domainkey." .. p.domain
    task:get_resolver():resolve_txt({
      task = task,
      name = resolve_name,
      callback = function(_, _, results, err)
        if not err and results and results[1] then
          p.pubkey = results[1]
          p.strict_pubkey_check = not settings.allow_pubkey_mismatch
        elseif not settings.allow_pubkey_mismatch then
          rspamd_logger.infox(task, 'public key for domain %s/%s is not found: %s, skip signing',
              p.domain, p.selector, err)
          return
        else
          rspamd_logger.infox(task, 'public key for domain %s/%s is not found: %s',
              p.domain, p.selector, err)
        end

        local sret, hdr = sign_func(task, p)
        insert_sign_results(task, sret, hdr, p)
      end,
      forced = true
    })
  else
    local sret, hdr = sign_func(task, p)
    insert_sign_results(task, sret, hdr, p)
  end
end

local function sign_error(task, msg)
  rspamd_logger.errx(task, 'signing failure: %s', msg)
end

local function dkim_signing_cb(task)
  local ret, selectors = dkim_sign_tools.prepare_dkim_signing(N, task, settings)

  if not ret then
    return
  end

  if settings.use_redis then
    dkim_sign_tools.sign_using_redis(N, task, settings, selectors, do_sign, sign_error)
  else
    if selectors.vault then
      dkim_sign_tools.sign_using_vault(N, task, settings, selectors, do_sign, sign_error)
    else
      if #selectors > 0 then
        for _, k in ipairs(selectors) do
          -- templates
          if k.key then
            k.key = lua_util.template(k.key, {
              domain = k.domain,
              selector = k.selector
            })
            lua_util.debugm(N, task, 'using key "%s", use selector "%s" for domain "%s"',
                k.key, k.selector, k.domain)
          end

          do_sign(task, k)
        end
      else
        rspamd_logger.infox(task, 'key path or dkim selector unconfigured; no signing')
        return false
      end
    end
  end
end

local opts = rspamd_config:get_all_opt('dkim_signing')
if not opts then
  return
end

dkim_sign_tools.process_signing_settings(N, settings, opts)

if not dkim_sign_tools.validate_signing_settings(settings) then
  rspamd_logger.infox(rspamd_config, 'mandatory parameters missing, disable dkim signing')
  lua_util.disable_module(N, "config")
  return
end

if settings.use_redis then
  redis_params = lua_redis.parse_redis_server('dkim_signing')

  if not redis_params then
    rspamd_logger.errx(rspamd_config,
        'no servers are specified, but module is configured to load keys from redis, disable dkim signing')
    lua_util.disable_module(N, "redis")
    return
  end

  settings.redis_params = redis_params
end

local sym_reg_tbl = {
  name = settings['symbol'],
  callback = dkim_signing_cb,
  groups = { "policies", "dkim" },
  flags = 'ignore_passthrough',
  score = 0.0,
}

if type(settings.allowed_ids) == 'table' then
  sym_reg_tbl.allowed_ids = settings.allowed_ids
end
if type(settings.forbidden_ids) == 'table' then
  sym_reg_tbl.forbidden_ids = settings.forbidden_ids
end

rspamd_config:register_symbol(sym_reg_tbl)
-- Add dependency on DKIM checks
rspamd_config:register_dependency(settings['symbol'], 'DKIM_CHECK')