mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-07 19:18:57 +00:00
message:recipient() may now return an array style table holding the recipient list, if there is more than a single recipient on the message. Since this can be somewhat ambiguous/frustrating to work with, there is now also a message:recipient_list() that will always return an array style table, even if it holds just a single element. The included helpers have been updated to use `message:recipient_list`. message:set_recipient() will now optionally accept an array style table holding the recipient list to be set.
185 lines
5.1 KiB
Lua
185 lines
5.1 KiB
Lua
local mod = {}
|
|
local kumo = require 'kumo'
|
|
local utils = require 'policy-extras.policy_utils'
|
|
|
|
local function all_hosts_match_suffix(hosts, suffix)
|
|
for _, host in ipairs(hosts) do
|
|
if not utils.ends_with(host, suffix) then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function compute_ip_rollup_mx_list(domain, routing_domain)
|
|
if routing_domain == nil then
|
|
return nil
|
|
end
|
|
if not utils.ends_with(routing_domain, '.ip_rollup') then
|
|
return nil
|
|
end
|
|
|
|
local mx = kumo.dns.lookup_mx(domain)
|
|
|
|
local lowest_pref = nil
|
|
local hosts = nil
|
|
|
|
for pref, pref_hosts in pairs(mx.by_pref) do
|
|
pref = tonumber(pref)
|
|
if lowest_pref == nil or pref < lowest_pref then
|
|
lowest_pref = pref
|
|
hosts = pref_hosts
|
|
end
|
|
end
|
|
|
|
local addrs = {}
|
|
-- The host names are pre-sorted by lookup_mx
|
|
for _, host in ipairs(hosts) do
|
|
local host_addrs = kumo.dns.lookup_addr(host)
|
|
-- The addresses are in an unspecified order.
|
|
-- Sort them so that we produce a consistent list
|
|
-- and hash consistently with the resulting ready queue name
|
|
table.sort(host_addrs)
|
|
for _, a in ipairs(host_addrs) do
|
|
table.insert(addrs, { name = host, addr = a })
|
|
end
|
|
end
|
|
|
|
return addrs
|
|
end
|
|
|
|
function mod.apply_ip_rollup_to_queue_config(domain, routing_domain, params)
|
|
local mx_list = compute_ip_rollup_mx_list(domain, routing_domain)
|
|
if mx_list then
|
|
params.protocol = {
|
|
smtp = {
|
|
mx_list = mx_list,
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
local function compute_ip_rollup(domain, mapping)
|
|
local mx = kumo.dns.lookup_mx(domain)
|
|
|
|
local lowest_pref = nil
|
|
local hosts = nil
|
|
|
|
-- Extract the lowest pref value (highest priority) set
|
|
-- of mx hosts from the mx records
|
|
for pref, pref_hosts in pairs(mx.by_pref) do
|
|
pref = tonumber(pref)
|
|
if lowest_pref == nil or pref < lowest_pref then
|
|
lowest_pref = pref
|
|
hosts = pref_hosts
|
|
end
|
|
end
|
|
|
|
-- Find a matching mapping entry
|
|
local routing_domain = nil
|
|
for suffix, routing_domain in pairs(mapping) do
|
|
if all_hosts_match_suffix(hosts, suffix) then
|
|
return routing_domain
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--[[
|
|
This function performs ip-based rollup for domains whose
|
|
MX records match certain suffixies.
|
|
|
|
The mapping parameter must be a map of mx hostname suffix
|
|
to a synthetic routing domain name, which must also end
|
|
with `.ip_rollup`.
|
|
|
|
The recipient domain is resolved for the supplied message,
|
|
and its lowest preferenced (eg: highest priority) mx records
|
|
are compared against the hostname suffix. If they all match,
|
|
then the message has its routing domain set to the routing
|
|
domain.
|
|
|
|
In order to use this successfully, you must also arrange
|
|
for your get_queue_config event to update the queue parameters:
|
|
|
|
```lua
|
|
kumo.on('get_queue_config', function(domain, tenant, campaign, routing_domain)
|
|
local params = {}
|
|
rollup.apply_ip_rollup_to_queue_config(domain, routing_domain, params
|
|
return kumo.make_queue_config(params)
|
|
end)
|
|
|
|
kumo.on('smtp_server_message_received', function(msg)
|
|
rollup.reroute_using_ip_rollup(msg, {
|
|
['.mail.protection.outlook.com.'] = 'outlook.ip_rollup',
|
|
})
|
|
end)
|
|
```
|
|
|
|
To apply shaping to the result, if you are using the shaping helper,
|
|
you will need to use the ip rollup routing domain:
|
|
|
|
```toml
|
|
["outlook.ip_rollup"]
|
|
mx_rollup = false
|
|
# shaping parameters here
|
|
```
|
|
|
|
]]
|
|
function mod.reroute_using_ip_rollup(msg, mapping)
|
|
local status, err = pcall(function()
|
|
local recip_domain = msg:recipient_list()[1].domain
|
|
local routing_domain = compute_ip_rollup(recip_domain, mapping)
|
|
msg:set_meta('routing_domain', routing_domain)
|
|
end)
|
|
if not status then
|
|
-- Log, but otherwise ignore failure to resolve the domain
|
|
print(string.format('while checking ip_rollup: %s', err))
|
|
end
|
|
end
|
|
|
|
--[[
|
|
This function considers a mapping of MX hostname suffixes
|
|
to equivalent routing domains.
|
|
|
|
The MX for the recipient domain is resolved, and the set
|
|
of MX host names are checked against the suffixes.
|
|
If every hostname matches a suffix in the mapping, it is
|
|
consider to be an overall match, and the `routing_domain`
|
|
meta value is set to the corresponding domain.
|
|
|
|
This causes the mail for the recipients to have separate
|
|
scheduled queues but to use the same ready_queue and
|
|
site_name when it comes to delivering the mail, allowing
|
|
you to enforce traffic shaping rules on the same entity.
|
|
|
|
To use, place something like this in your
|
|
`smtp_server_message_received` and/or
|
|
`http_message_received` event handlers:
|
|
|
|
rollup.reroute_based_on_mx_host_suffix(msg, {
|
|
[".olc.protection.outlook.com."] = "outlook.com",
|
|
})
|
|
|
|
]]
|
|
function mod.reroute_based_on_mx_host_suffix(msg, mapping)
|
|
local status, err = pcall(function()
|
|
local recip_domain = msg:recipient_list()[1].domain
|
|
local mx = kumo.dns.lookup_mx(recip_domain)
|
|
for suffix, routing_domain in pairs(mapping) do
|
|
if all_hosts_match_suffix(mx.hosts, suffix) then
|
|
if recip_domain ~= routing_domain then
|
|
msg:set_meta('routing_domain', routing_domain)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end)
|
|
if not status then
|
|
-- Log, but otherwise ignore failure to resolve the domain
|
|
print(string.format('while checking mx host pattern: %s', err))
|
|
end
|
|
end
|
|
|
|
return mod
|