mirror of
https://github.com/mailscope/kumomta.git
synced 2026-10-29 01:36:14 +00:00
190 lines
4.6 KiB
Lua
190 lines
4.6 KiB
Lua
local mod = {}
|
|
local kumo = require 'kumo'
|
|
local utils = require 'policy-extras.policy_utils'
|
|
|
|
--[[
|
|
Usage:
|
|
|
|
Create a `/opt/kumomta/etc/shaping.json` with contents like:
|
|
|
|
```json
|
|
{
|
|
"gmail.com": {
|
|
// "mx_rollup": false, // if false, is NOT shared with other domains with same mx
|
|
"connection_limit": 3,
|
|
|
|
// Entries that are themselves objects are considered to be
|
|
// source configuration. These will take precendence over
|
|
// the more general configuration for the domain/site
|
|
"sources": {
|
|
"source-0": {
|
|
"connection_limit": 5
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
local shaping = require 'policy-extras.shaping'
|
|
kumo.on('get_egress_path_config', shaping:setup('/opt/kumomta/etc/shaping.json'))
|
|
---
|
|
|
|
Alternatively, you could use a TOML file instead.
|
|
]]
|
|
|
|
function mod:setup(extra_files)
|
|
local function load_shaping_data(file_names)
|
|
local site_to_domains = {}
|
|
local result = {
|
|
by_site = {},
|
|
by_domain = {},
|
|
}
|
|
for _, filename in ipairs(file_names) do
|
|
local data = utils.load_json_or_toml_file(filename)
|
|
-- print('Loaded', kumo.json_encode_pretty(data))
|
|
for domain, config in pairs(data) do
|
|
local entry = {
|
|
sources = {},
|
|
params = {},
|
|
}
|
|
|
|
local mx_rollup = true
|
|
if config.mx_rollup ~= nil then
|
|
mx_rollup = config.mx_rollup
|
|
end
|
|
local replace_base = false
|
|
if config.replace_base ~= nil then
|
|
replace_base = config.replace_base
|
|
end
|
|
|
|
config.mx_rollup = nil
|
|
config.replace_base = nil
|
|
|
|
if domain == 'default' then
|
|
mx_rollup = false
|
|
end
|
|
|
|
for k, v in pairs(config) do
|
|
if k == 'sources' then
|
|
entry.sources = v
|
|
else
|
|
entry.params[k] = v
|
|
end
|
|
end
|
|
|
|
if mx_rollup then
|
|
local site_name = kumo.dns.lookup_mx(domain).site_name
|
|
|
|
if site_name == '' then
|
|
error(
|
|
string.format(
|
|
'domain %s has a NULL MX and cannot be used with mx_rollup=true',
|
|
domain
|
|
)
|
|
)
|
|
end
|
|
|
|
if not replace_base then
|
|
utils.merge_into(result.by_site[site_name], entry)
|
|
end
|
|
|
|
result.by_site[site_name] = entry
|
|
|
|
local site_domains = site_to_domains[site_name] or {}
|
|
site_domains[domain] = true
|
|
site_to_domains[site_name] = site_domains
|
|
else
|
|
if not replace_base then
|
|
utils.merge_into(result.by_domain[domain], entry)
|
|
end
|
|
|
|
result.by_domain[domain] = entry
|
|
end
|
|
end
|
|
end
|
|
|
|
local conflicted = {}
|
|
for site, domains in pairs(site_to_domains) do
|
|
domains = utils.table_keys(domains)
|
|
if #domains > 1 then
|
|
domains = table.concat(domains, ', ')
|
|
table.insert(conflicted, domains)
|
|
print(
|
|
'Multiple domains rollup to the same site: '
|
|
.. site
|
|
.. ' -> '
|
|
.. domains
|
|
)
|
|
end
|
|
end
|
|
|
|
if #conflicted > 0 then
|
|
-- This will generate a transient failure for every message
|
|
-- until the issue is resolved
|
|
error(
|
|
'multiple conflicting rollup domains '
|
|
.. table.concat(conflicted, ' ')
|
|
)
|
|
end
|
|
|
|
-- print('Computed', kumo.json_encode_pretty(result))
|
|
return result
|
|
end
|
|
|
|
local cached_load_data = kumo.memoize(load_shaping_data, {
|
|
name = 'shaping_data',
|
|
ttl = '5 minutes',
|
|
capacity = 10,
|
|
})
|
|
|
|
local file_names = {
|
|
-- './assets/policy-extras/shaping.toml',
|
|
'/opt/kumomta/share/policy-extras/shaping.toml',
|
|
}
|
|
if extra_files then
|
|
for _, filename in ipairs(extra_files) do
|
|
table.insert(file_names, filename)
|
|
end
|
|
end
|
|
|
|
local function get_egress_path_config(domain, egress_source, site_name)
|
|
local data = cached_load_data(file_names)
|
|
|
|
local by_site = data.by_site[site_name]
|
|
local by_domain = data.by_domain[domain]
|
|
|
|
local params = {}
|
|
|
|
-- apply basic/default configuration
|
|
utils.merge_into((data.by_domain['default'] or {}).params, params)
|
|
|
|
-- then site config
|
|
if by_site then
|
|
utils.merge_into(by_site.params, params)
|
|
end
|
|
-- then domain config
|
|
if by_domain then
|
|
utils.merge_into(by_domain.params, params)
|
|
end
|
|
|
|
-- then source config for the site
|
|
if by_site then
|
|
utils.merge_into(by_site.sources[egress_source], params)
|
|
end
|
|
|
|
-- then source config for the domain
|
|
if by_domain then
|
|
utils.merge_into(by_domain.sources[egress_source], params)
|
|
end
|
|
|
|
-- print("going to make egress path", kumo.json_encode(params))
|
|
|
|
return kumo.make_egress_path(params)
|
|
end
|
|
|
|
return get_egress_path_config
|
|
end
|
|
|
|
return mod
|