mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-07 19:18:57 +00:00
This aggregates the various authentication-results producing auth checks into a more convenient function. Part of this change improves some plumbing in the dkim checking (we no longer raise an error for a missing From, but instead indicate a failed dkim result), and we now support passing down a resolver name to the various checking functions, to facilitate testing and other more advanced use cases. refs: https://github.com/KumoCorp/kumomta/issues/16 refs: https://github.com/KumoCorp/kumomta/issues/84
294 lines
6.1 KiB
Lua
294 lines
6.1 KiB
Lua
local mod = {}
|
|
local kumo = require 'kumo'
|
|
|
|
local function is_table_or_ud(o)
|
|
local ot = type(o)
|
|
if ot == 'table' then
|
|
return true
|
|
end
|
|
if ot == 'userdata' then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function has_mt(object, name)
|
|
local mt = getmetatable(object)
|
|
if not mt then
|
|
return false
|
|
end
|
|
if mt[name] then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
---@param o1 any|table First object to compare
|
|
---@param o2 any|table Second object to compare
|
|
---@param ignore_mt boolean True to ignore metatables (a recursive function to tests tables inside tables)
|
|
-- https://stackoverflow.com/a/32660766/149111
|
|
function mod.equals(o1, o2, ignore_mt)
|
|
if o1 == o2 then
|
|
return true
|
|
end
|
|
if not is_table_or_ud(o1) then
|
|
return false
|
|
end
|
|
if not is_table_or_ud(o2) then
|
|
return false
|
|
end
|
|
|
|
if not ignore_mt then
|
|
local mt1 = getmetatable(o1)
|
|
if mt1 and mt1.__eq then
|
|
--compare using built in method
|
|
return o1 == o2
|
|
end
|
|
end
|
|
|
|
local keySet = {}
|
|
|
|
for key1, value1 in pairs(o1) do
|
|
local value2 = o2[key1]
|
|
if value2 == nil or mod.equals(value1, value2, ignore_mt) == false then
|
|
return false
|
|
end
|
|
keySet[key1] = true
|
|
end
|
|
|
|
for key2, _ in pairs(o2) do
|
|
if not keySet[key2] then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function mod.assert_eq(a, b, message)
|
|
if not mod.equals(a, b) then
|
|
if message then
|
|
message = string.format('%s. ', message)
|
|
else
|
|
message = ''
|
|
end
|
|
error(
|
|
string.format(
|
|
'%sexpected\n%s\n == \n%s',
|
|
message,
|
|
kumo.json_encode(a),
|
|
kumo.json_encode(b)
|
|
),
|
|
2
|
|
)
|
|
end
|
|
end
|
|
|
|
function mod.assert_ne(a, b, message)
|
|
if mod.equals(a, b) then
|
|
if message then
|
|
message = string.format('%s. ', message)
|
|
else
|
|
message = ''
|
|
end
|
|
error(
|
|
string.format(
|
|
'%sexpected\n%s\n != \n%s',
|
|
message,
|
|
kumo.json_encode(a),
|
|
kumo.json_encode(b)
|
|
),
|
|
2
|
|
)
|
|
end
|
|
end
|
|
|
|
function mod.assert_matches(a, pattern, message)
|
|
local re = kumo.regex.compile(pattern)
|
|
if not re:is_match(a) then
|
|
if message then
|
|
message = string.format('%s. ', message)
|
|
else
|
|
message = ''
|
|
end
|
|
error(
|
|
string.format(
|
|
'%sexpected\n "%s"\n to match\n "%s"',
|
|
message,
|
|
a,
|
|
pattern
|
|
),
|
|
2
|
|
)
|
|
end
|
|
end
|
|
|
|
-- Helper function that merges the values from `src` into `dest`
|
|
function mod.merge_into(src, dest)
|
|
if src then
|
|
for k, v in pairs(src) do
|
|
dest[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Helper function that merges the values from `src` into `dest`
|
|
function mod.recursive_merge_into(src, dest)
|
|
if src then
|
|
for k, v in pairs(src) do
|
|
if type(v) == 'table' then
|
|
if not dest[k] then
|
|
dest[k] = v
|
|
else
|
|
mod.recursive_merge_into(v, dest[k])
|
|
end
|
|
else
|
|
dest[k] = v
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Returns true if the table tbl contains value v
|
|
function mod.table_contains(tbl, v)
|
|
if v == nil then
|
|
return false
|
|
end
|
|
for _, value in pairs(tbl) do
|
|
if value == v then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Helper function to return the keys of a object style table as an array style
|
|
-- table
|
|
function mod.table_keys(tbl)
|
|
local result = {}
|
|
for k, _v in pairs(tbl) do
|
|
table.insert(result, k)
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Returns true if table is empty, false otherwise
|
|
function mod.table_is_empty(tbl)
|
|
for _k, _v in pairs(tbl) do
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function mod.load_json_or_toml_file(filename)
|
|
if type(filename) == 'table' then
|
|
-- allow inline lua objects to be passed through as-is
|
|
return filename
|
|
end
|
|
if filename:match '.toml$' then
|
|
return kumo.toml_load(filename)
|
|
end
|
|
return kumo.json_load(filename)
|
|
end
|
|
|
|
-- Returns true if the string `s` starts with the string `text`
|
|
function mod.starts_with(s, text)
|
|
return string.sub(s, 1, #text) == text
|
|
end
|
|
|
|
-- Returns true if the string `s` ends with the string `text`
|
|
function mod.ends_with(s, text)
|
|
return string.sub(s, -#text) == text
|
|
end
|
|
|
|
-- Given an IP:port, split it into a tuple of IP and port
|
|
function mod.split_ip_port(ip_port)
|
|
-- "127.0.0.1:25" -> "127.0.0.1", "25"
|
|
-- "[::1]:25" -> "[::1]", "25"
|
|
local ip, port = string.match(ip_port, '^(.*):(%d+)$')
|
|
|
|
-- If we have `[ip]` for ip, remove the square brackets
|
|
local remove_square = string.match(ip, '^%[(.*)%]$')
|
|
if remove_square then
|
|
return remove_square, port
|
|
end
|
|
|
|
return ip, port
|
|
end
|
|
|
|
local function dump_impl(value, indent, done)
|
|
if done[value] then
|
|
return '[recursive node]'
|
|
end
|
|
|
|
local ty = type(value)
|
|
|
|
if ty == 'table' then
|
|
if has_mt(value, '__tostring') then
|
|
local s = tostring(value)
|
|
if s then
|
|
return s
|
|
end
|
|
end
|
|
|
|
done[value] = true
|
|
|
|
local list = {}
|
|
for key in pairs(value) do
|
|
table.insert(list, key)
|
|
end
|
|
table.sort(list)
|
|
|
|
local dumped = '{'
|
|
for idx, key in ipairs(list) do
|
|
if idx > 1 then
|
|
dumped = dumped .. ',\n'
|
|
else
|
|
dumped = dumped .. '\n'
|
|
end
|
|
|
|
dumped = dumped .. string.rep(' ', indent)
|
|
dumped = dumped
|
|
.. string.format(
|
|
'[%q] = %s',
|
|
key,
|
|
dump_impl(value[key], indent + 2, done)
|
|
)
|
|
end
|
|
done[value] = false
|
|
if #dumped > 1 then
|
|
dumped = dumped .. '\n' .. string.rep(' ', indent - 2)
|
|
end
|
|
dumped = dumped .. '}'
|
|
return dumped
|
|
end
|
|
|
|
if ty == 'number' or ty == 'boolean' then
|
|
return tostring(value)
|
|
end
|
|
|
|
return string.format('%q', tostring(value))
|
|
end
|
|
|
|
-- Returns a psuedo-lua repr style dump of the provided value
|
|
-- as a string. This is useful for getting a sense of what the
|
|
-- value might be during debugging
|
|
function mod.dumps(value)
|
|
if value == nil then
|
|
return nil
|
|
end
|
|
local done = {}
|
|
return dump_impl(value, 2, done)
|
|
end
|
|
|
|
function mod:test()
|
|
mod.assert_eq(mod.dumps(true), 'true')
|
|
mod.assert_eq(mod.dumps(false), 'false')
|
|
mod.assert_eq(mod.dumps(42), '42')
|
|
mod.assert_eq(mod.dumps(1.5), '1.5')
|
|
mod.assert_eq(mod.dumps {}, '{}')
|
|
mod.assert_eq(mod.dumps { 1, 2 }, '{\n [1] = 1,\n [2] = 2\n}')
|
|
mod.assert_eq(mod.dumps { foo = 'bar' }, '{\n ["foo"] = "bar"\n}')
|
|
end
|
|
|
|
return mod
|