Commit Graph
53 Commits
Author SHA1 Message Date
Wez Furlong dd41e999c4 introduce declare_metric! macro
This reduces the boilerplate around declaring metrics
(counters, histograms, gauges) in their various forms,
and more or less standardizes them, making the syntax
more regular regardless of how the metrics are actually
stored.

We move the help out to doc comments, making it easier
to write a multi-line exposition on a given metric (in
the future; we're not doing that yet).

linkme is used to form a registry that can be used to
eagerly collect metadata from the various metrics.  This
will be used to drive some automated documentation
extraction for the various metrics in a future commit.
2026-02-04 09:10:25 +00:00
Wez Furlong 07ab3d44a3 inject+xfer: cut over to AuthInfo from AuthKind
Replace the older type with the newer one.

Serialize a copy of auth_info when handling deferred generation,
so that we have a lossless representation of that state when
we eventually process the request.  That doesn't change really
anything today, but will enable more granular ACL checks in
the future.

Pass the auth_info through to the http_message_generated and
xfer_message_received events to enable more granular access
control policies to be scripted.
2025-12-18 10:19:09 +00:00
Wez Furlong 3e85297fca authn: allow returning AuthInfo from smtp/http auth event callbacks
This commit allows the policy to return a richer representation of
the authentication information, which can include multiple identities
and group membership information.
2025-12-18 06:30:06 +00:00
Wez Furlong 2a52388a98 config: refactor callback calling
Centralize the logic of walking and calling the registered handlers
so that we don't have 3 or more slightly different versions of it.

Introduce a CallbackDisposition type that can be used to drive
the default handling with more nuance; this will be useful
in a later commit where we need to distinguish between an
explicit nil being returned by a handler, and no handler
being defined.
2025-12-18 06:30:05 +00:00
Wez Furlong 47aac10ea6 data-loader: allow callback/event based loading of secrets
This allows a (more or less) arbitrary lua function to be used to load
data.

The primary advantage of this is that the size of the resulting
KeySource struct is smaller than it would be to hold the actual data
inline.  It also enables the surrounding code to be slightly better
factored.
2025-10-24 13:41:27 +01:00
Wez Furlong 4be75084e1 deps: paste -> pastey
The paste crate is unmaintainted, pastey is a maintained fork:
https://rustsec.org/advisories/RUSTSEC-2024-0436
2025-10-08 09:37:31 +01:00
Wez Furlong 1bf9d138ce extended lua mime parsing API
refs: https://github.com/KumoCorp/kumomta/issues/117

Could also make it possible to do eg:
https://github.com/KumoCorp/kumomta/issues/142
without any explicit direct support in the product.
2025-09-09 09:15:58 +01:00
Wez Furlong e3fdcaa628 cargo clippy --fix 2025-04-09 10:26:07 -07:00
Wez Furlong 70d9ec394a config: introduce declare_event! macro
There should be no functional change here; this commit refactors
how we pre-declare CallbackSignatures with lua. The rationale is:

* It is important to correct declare single vs. multiple implementation
  event handler types before any lua code is run; we've had a couple
  of issues in the past where some part of this pre-registration
  was messed up.
* Adding a new signature requires declaring it in a global, and
  remembering to add its registration to the right place
* Declaring signatures is a bit boilerplatey and makes it hard
  grok the purpose of the event handler arguments at a glance

The declare_event! macro defined in this commit makes it a bit
more readable to declare these event types and automatically
wires up the registration to the correct spot, improving the
ergonomics significantly.

A downside of this additional layer of macro stuff that it
requires increasing the rustc recursion limit.
2025-03-06 06:35:38 -07:00
Wez Furlong 2bd7410ca6 config: fix cancellation safety issue with async lua calls
This commit addresses a tricky interaction with lua's garbage collection
and cancellation safety of async lua call handling.

In Rust, when a future is cancelled it is simply not called any more,
and any resources it owns will be released when it is dropped.

In mlua's Rust bindings, async calls are futures that are wrapped
up into state whose ownership is managed in the lua context
stack, which in turn has managed via its own garbage collection
process.

In KumoMTA, we implement a pool of Lua contexts which we keep
around for reuse.  Our logic will return our LuaConfig object
back to this pool automatically when it is dropped.

In a situation where tokio timeouts or select! are used with
a future that transitively contains a lua async call and that
call is cancelled, that can leave async locks/semaphores owned
in the un-collected objects in the lua stack.

For async locks/semaphores that were in the locked state, this
can leave them locked until the owning lua context is expired
from the pool, either due to age, capacity or use count being hit.

That's bad because it can block certain processing flows for 5 minutes
(the default `kumo.set_max_lua_context_age`).

What this commit does is switch away from automatically returning
the LuaConfig to the pool on Drop.  In that case the lua context
is destroy on Drop, immediately allowing destructors to release
any/all owned resources.

We do still want to benefit from the pool, so the bulk of
this commit is changing the consumers of load_config to
explicitly call `config.put()` to put the object back into
the pool, but only in the success case.

This does penalize flows that grab a config and then
`kumo.reject` as their final status. At the present time, the
majority of kumomta deployments are very much focused on (relatively)
trusted senders and an outbound use case, so this isn't a terrible
situation for the time being.
2025-03-04 16:23:13 -07:00
Wez Furlong f06d7bb465 lua: add __len metamethod for serializable userdata
This is added for consistency with the __pairs and __index metamethods.
2025-03-04 16:23:11 -07:00
Wez Furlong 8042f7b07a config: invalid pooled lua context when epoch changes
I noticed while testing the throttle serialization commit
that precedes this one that my directly-in-the-lua-file config
changes weren't being picked up when the config epoch changed.

This commit revises the pooling logic to also check the epoch
in addition to the age of the pooled entries.
2025-01-29 11:24:36 -07:00
Wez Furlong 9cbef3b202 fix deserialize error: unknown field relay_from_authz issue
The changes in e4b743cb8f to improve
memoize performance could cause the error message above because
the __newindex method wasn't actually writing the new value to
the unshared table, it was returning the existing value!

Most of this commit is really plumbing to support calling through the
cache layers in the same way that the listener would use, but in the
context of the unit tests in the lua file.

A nice side effect of this plumbing is that it is now possible
to iterate (via pairs) and index fields of the main config
objects that are returned by the most common `kumo.make_xxx`
functions.
2025-01-13 16:51:38 -07:00
Wez Furlong 886b4574ea config: from_lua_value will now fallback to __pairs
from_lua_value is used to attempt to deserialize a generic lua
value into a concrete Rust type that is known at compile time.

This uses some support logic inside mlua to allow:

* If it is a userdata created by mlua, mlua will try to retrieve
  that concrete rust type if the type of the userdata exactly
  matches.
* otherwise: it will use serde to deserialize the lua value
  into the rust type. mlua's deserializer cannot process
  userdata, so if we still have on at this point, the attempt
  to convert it will fail.

That's mostly fine, but our memoize crate will start returning a proxy
userdata type in a future commit, and since that proxy type will never
match the concrete Rust type that is desired by the above logic, it will
become impossible to pass memoized values without some kind of explicit
preprocessing.

What this commit does is add implicit preprocessing of the value: if we
cannot use the default mlua userdata cast, then, if the value is a
userdata, we will iterate over its fields to build a native lua
representation of that value. We also apply this translation by
traversing tables and converting their values, recursively.

While this is "preprocessing", it is only applied in case the regular
from_lua_value logic in mlua (the fast path) fails, so this is fallback
preprocessing rather than eager preprocessing, which should be
performance neutral in the common hot code paths.
2025-01-11 06:31:22 -07:00
Wez Furlong 62672a2fbd deps: upgrade mlua to 0.10
This is a reasonably significant upgrade, as it allows for
async functions to be Send, which could unlock more efficiencies
in our overall scheduling.

This commit is just the basic changes required to get the updated
mlua version compiling, it doesn't change anything else.
2024-12-07 09:17:54 -07:00
Wez Furlong fbd794782a bounce classifier: reload classifier when config epoch changes
This change plugs the bounce classification layer into the config
epoch layer, so that when a change in the configuration is detected,
we reload the classifier using the previously established parameters
and then arrange for the classifier threads to wake up and update
their local classifiers from that updated state.

refs: https://github.com/KumoCorp/kumomta/issues/298
2024-10-09 08:18:00 -07:00
Wez Furlong d063c96b5f yell loudly when sig.register() should have been called
A number of our lua event handlers allow registering multiple
implementations to facilitate modular use.

For that to work, we must know ahead of the user lua call running
that any given handle is allowed to register multiple times.  This is so
that we can report a meaningful error when incorrectly using a singleton
handler multiple times, and so that we can record a list of handlers
for the multiple case.

Prior to this commit, if we forgot to arrange to register the signature
as part of the context setup the consequence was that the event handler
would get registered as a singleton and when we went to call it, because
the signature is marked as allowing multiple but was not registered
as multiple, we would skate through and do nothing without reporting
an error because we assumed that the signature was registered
consistently.

In hindsight, that's a terrible idea because it results in silently
ignoring the registration issue, and not calling the event handler
at all.

This commit consolidates the multiple/single value resolution into
the same flow, then adds a check to confirm that we have a list
of handlers registered for the allow_multiple case, raising an
error otherwise that will hopefully encourage users to report
this problem to us if it manifests again in the future.

This commit includes fixing two event handlers that we missing
their signature registration.

One of them was broken anyway by being registered with a name
that didn't match the docs.

refs: https://github.com/KumoCorp/kumomta/issues/236
2024-10-07 10:25:47 -07:00
Dirkjan Ochtman bf896f5365 Replace once_cell with LazyLock/OnceLock 2024-09-24 06:31:21 -07:00
Dirkjan Ochtman 4df3bfd471 Replace lazy-static with LazyLock 2024-09-24 06:31:21 -07:00
Dirkjan Ochtman 0208f3d2de config: avoid inferred never return type 2024-09-16 06:32:33 -07:00
Wez Furlong 64fac57d09 formalize provider information for queues, add to logs
The recent changes to enable shaping based on a pattern-matched provider
are nice, but it is important to be able to observe their effects.

So far this has been awkward because the provider concept was purely a
function of the logic in the shaping.lua file and nothing else.

This commit introduces the concept of a `provider_name` field in
both the EgressPathConfig and QueueConfig structs.

The idea is that the `get_egress_path_config` and `get_queue_config`
events are free to populate this field as makes sense to them, so that
the core is then aware of which provider is associated with those
queues.

Once we have that data, we're then able to log it as a field in the
JsonLogRecord.

That is what this commit does. There are some interesting points to note
about the implementation here:

1. shaping.lua will implicitly assign provider_name if it matches
   any providers.

2. It is technically possible for a shaping.toml to define multiple
   providers that match a given domain. In that circumstance, the
   last matching provider is the winner when it comes to assigning
   the provider_name field.

3. In order to populate the provider_name in the queue.lua helper,
   we need to be able to call out to the get_egress_path_config
   event handlers, so a new kumo.invoke_get_egress_path_config
   has been added to support that.

4. kumo.invoke_get_egress_path_config isn't 100% done: there are
   a couple of fields (openssl related) that don't have a defined
   serializer, so we're simply omitting them.  The function is
   "done enough" for the purposes of retrieving the provider_name

refs: https://github.com/KumoCorp/kumomta/issues/276
2024-09-12 20:51:00 -07:00
Wez Furlong f7e68070d1 config: introduce ConfigEpoch and policy file monitoring
This commit adds a background task that periodically evaluates
a glob expression that defaults to the recommended configuration
location and filename suffixes, and a set of additional paths
to observe.

Whenever the hash of that combined set of files changes it causes the
ConfigEpoch to increment and broadcast to subscribers that the
configuration has changed in some fashion.

The QueueConfig struct has a new refresh_strategy which can select
between the earlier Ttl based refresh for the queue config, or
the new Epoch refresh.

When the epoch changes, the config refresh task will cause each of
the scheduled queues that is using the Epoch strategy to re-evaluate
the get_queue_config event to update their configuration.

The queues helper sets the refresh strategy to Epoch.

A new HTTP endpoint has been added: it can force a bump in the
current epoch, effectively causing all epoch subscribers to
wake up and perform a refresh.

These changes avoid doing O(number-of-scheduled-queues) get_queue_config
callouts every refresh_interval; instead, the work is performed only
when an appropriate change is detected or triggered.
2024-09-02 10:42:14 -07:00
Wez Furlong 84a1e8a5e7 config: remove deserialie error log
It turns out that this particular class of error is never logged,
despite the comment here about unconditionally logging, because
the default log filter excludes.

I found this because in the next commit I enable logging for
the config crate and I've found that the various checks to see
if a parameter is valid performed by the helpers will actually
trigger this log line all the time, so it has to go.
2024-09-02 10:42:14 -07:00
Wez Furlong afd04e82e3 config: add gc-on-put chance
In some respects, this is conceptually redundant or similar with
the option to drop after a certain number of requests, but there
are two important differences:

* This is probabalistic rather than setting an absolute threshold,
  so it should scale with the traffic
* Rather than dropping the entire context, we just gc it, allowing
  it to be reused later.  Depending on how much garbage was collected,
  this is probably cheaper than creating a new context from scratch
  in a complex configuration.

The way this option works is that you set a percentage chance for
a gc to happen when a context is returned to the pool from 0-100.

A random number is generated and if it falls below the chance, then
a gc run is triggered.

Setting this value large can harm performance.

I've found that setting it to 1 has minimal impact while also
helping to keep the un-reaped Message handles to a bounded and
more reasonable level.

This option defaults to 0.
2024-08-23 17:31:46 -07:00
Wez Furlong 81e0bdcbbe config: add lua_event_started counter
This is bumped each time we're about to call out to a lua event.
The idea is that you can compare the corresponding
lua_event_latency_count to get a sense of the number events that
have not yet completed and that might be blocked or otherwise
running slower than desired.
2024-08-22 11:07:08 -07:00
Wez Furlong 8d29df10d6 config: record and export latency metrics for lua event callbacks 2024-08-15 14:55:02 -07:00
Wez Furlong 0cfba6c564 config: refactor: split Pool into its own module
No functional change
2024-08-14 08:49:09 -07:00
Wez Furlong c524ef5d11 deps: upgrade mlua from 0.8 -> 0.9
I've been holding off on this for a while, because I remember
this upgrade being a bit of a PITA for wezterm.  Since then
there is a `derive(FromLua)` that makes the transition much
easier.

This upgrade makes it potentially a bit easier/nicer/cheaper for
memoize, because it is now possible to retrieve references to userdata
values (rather than always cloning them), but I haven't done anything
special to take advantage of that in this commit.
2024-06-28 17:28:42 -07:00
Wez Furlong 3139be2cfd Add new kumod --validate flag
The purpose is to provide a deeper, offline validation pass
of the policy configuration, prior to deploying and making it
live.

The system behavior changes when in `--validate` mode:

* Listeners, spool and spawned tasks will be silently skipped;
  the parameters will be validated but the primary functions
  of those things will be skipped silently.
* After triggering the `init` event, an additional new `validate_config`
  event (which can be registered multiple times) will be triggered
  to allow lua modules to perform extended validation.
* A module can either raise an error via `error` to immediately report
  a problem, or instead call a new, preferred, `kumo.validation_failed()`
  function to flag validation as failed but allow additional validation
  to be performed and summarized all together.
* Once the `validate_config` event returns, the process will terminate
  with either exit code 0 for a successful validation, or non-zero
  to indicate that something failed.

Validation errors are reported in a human readable form.

This commit adds validate_config event handlers for the following
helper modules:

* `shaping` - any warnings reported by the underlying rust code
  will be reported here and cause validation to fail. This is
  functionally equivalent to using the `validate-shaping` binary,
  except that it will automatically be passed the set of shaping
  files defined by your `init.lua`

  If the `sources` helper is also configured, the list of sources
  referenced by the shaping config will be cross-checked against
  the sources data to confirm that all possible sources are defined.

* `sources` - each listed source and pool will be validated by
  calling `kumo.make_egress_source` or `kumo.make_egress_pool`
  respectively.

  Pool membership will be validated to confirm that every
  listed pool is defined in the sources data.

* `queues` - each domain and tenant that references an egress_pool
  will be cross-checked with the `sources` helper, if the sources
  helper has been configured.

It is now an error to attempt to setup any of the above helpers
more than once.

refs: https://github.com/KumoCorp/kumomta/issues/211
2024-06-25 11:05:04 -07:00
Wez Furlong a2d76df7a9 NEW: rebind API and kcli subcommand
refs: https://github.com/KumoCorp/kumomta/issues/209
2024-06-24 12:26:06 -07:00
Wez Furlong 86bde42c2f config: Mutex -> parking_lot::FairMutex 2024-05-07 13:15:45 -07:00
Wez Furlong f0fdec1eb0 fixup and fully migrate to metrics 0.22
Commit 12fe5e3b61 updated the
metrics related crates, but not in every one of our crates,
which meant that we were running with a mixture of 0.20
and 0.22.

The result of this was that the memory related metrics
were no long visible to the published prometheus data
because it was only looking at the other version of
the metrics crate!

This commit switches over to using a workspace dep
for metrics so that we update all related crates
together.
2024-04-10 10:02:29 -07:00
Wez Furlong 39de0e33a6 Expose suspension API to lua
This is very similar to the HTTP suspension API, with the
difference that the suspend method returns just the uuid rather
than the entire suspension object.

refs: https://github.com/KumoCorp/kumomta/issues/113
2024-03-28 07:14:35 -07:00
Wez Furlong 6003976a10 new: kumo.spawn_task function
This function will spawn a new thread that runs a tokio
localset, which in turn will trigger the specified event
and run it.

On it's own it doesn't do a lot, but it provides a way
to perform background tasks in lua.

```lua
kumo.on('init', function()
  kumo.spawn_task {
    event_name = 'my.task',
    args = { 'hello', 'there' },
  }
end)

kumo.on('my.task', function(args)
  -- Prints: `I am the task.  ["hello","there"]`
  print('I am the task.', kumo.json_encode(args))
end)
```
2024-03-28 07:14:35 -07:00
Wez Furlong e9ec19fd23 fix an issue where msg:[sg]et_meta would not round-trip nil values 2023-12-13 14:21:12 -07:00
Wez Furlong 929d51deba events: plumbing for registering multiple handlers
Previously, we'd restrict `kumo.on` to allowing just a single
instance of an event to be registered. The purpose of this was
to help surface logical errors where copypasta would result in
a bogus configuration.

With multiple helper lua modules now wanting to take responsibility
for some portion of the event handling, it is becoming more complex
to stitch things together.

It is desirable to allow multiple handlers for certain events,
so that a module can handle just its area of responsibility
without worry other modules about it.

This commit introduces a CallbackSignature type that allows
defining the function signature for event callbacks.

The signature can be pre-created and registered ahead of setting
up any lua contexts, which allows declaring whether an event
can have multiple callbacks registered.

The `get_queue_config` event handler has been set to allow multiple
callbacks.
2023-11-02 18:11:17 -07:00
Wez Furlong 786e053ba5 catch kumo.on called inside kumo.on
Event handlers must be registered at the file scope and not from
within another event handler in order for events to be consistently
triggered and handled.

In particular, the `init` event is only ever triggered once on
server startup.  If other handlers are registered from within the
init event, those will only ever fire when we re-use that original
lua context, which is good for a limited number of uses before it
is aged out of the resource pool.  That might work under very
limited or lightweight testing scenarios, but will otherwise
result in very inconsistent behavior.

We now catch and prevent, with a very visible error, attempting
to call `kumo.on` from within an event handler.
2023-08-16 16:21:51 -07:00
Wez Furlong d60c961479 increase lua pool limits, allow configuring it 2023-06-16 10:03:49 -07:00
Wez Furlong 21fc29bba8 make it a deserialize error when invalid fields are present
It turns out that ehlo_domain isn't valid for make_egress_source,
even though that is what we'd documented.

This commit surfaces this class of error visibly.

refs: https://github.com/KumoCorp/kumomta/issues/59
2023-06-16 08:33:39 -07:00
Wez Furlong 0c70ab2c52 improve error message when converting lua -> rust types
refs: https://github.com/KumoCorp/kumomta/issues/56
2023-06-14 20:14:14 -07:00
Wez Furlong ce7dece927 move domains out of listener, replace with get_listener_domain event
This commit also includes a policy helper
`policy_extras.listener_domains` to make it convenient to define
listener domains in toml and/or json files.

To facilitate this, the DomainMap rust structure has been exposed
to lua code via the new `kumo.domain_map.new` function.
2023-05-31 12:29:44 -07:00
Wez Furlong b7c557c31a define_egress_(pool|source) -> make_egress_(pool|source)
Switch the configuration plumbing for pools and sources to be pull-based
rather than push based.

In other words, rather than defining them in the `init` event,
you now need to supply them to the new `get_egress_pool` and
`get_egress_source` events.

Data is cached by default for 1 minute. This allows for new sources
to come into being on-demand, and for data to age out and change
over time, without requiring that the server be restarted.

This commit updates the reference section, but there is some content
in the user guide that refers to the old style of configuration that
will need to be updated.

refs: https://github.com/KumoCorp/kumomta/issues/13
2023-05-30 14:08:07 -07:00
Wez Furlong ac4d645670 Basic plumbing for handling deliveries in lua
```lua
-- Constructs a connection object in lua
kumo.on('make.lua-sender', function(domain, tenant, campaign)
  print 'making lua sender'
  local sender = {}
  function sender:send(message)
    print('Sending a message!', message)

    -- To log a transient failure:
    -- kumo.reject(420, 'boo!')

    -- To log a permanent failure:
    -- kumo.reject(500, "perm fail")

    -- To log success, return a string with additional data
    -- to be logged
    return 'Super!'
  end
  return sender
end)

-- Make everything use the lua "connection":
kumo.on('get_queue_config', function(domain, tenant, campaign)
  return kumo.make_queue_config {
    protocol = {
      custom_lua = {
        constructor = 'make.lua-sender',
      }
    },
  }
end)
```
2023-05-04 13:51:37 -07:00
Wez Furlong 278bc81aa0 move shaping -> policy-extras/shaping, and deploy with package
The assets/policy-extras dir is now deployed to
`/opt/kumomta/share/policy-extras` and added to the require path,
so you can do:

```lua
local shaping = require 'policy-extras.shaping'
```

to pull it into your policy.
2023-04-26 14:51:52 -07:00
Wez Furlong 4b9c2f5238 fix default policy and include path to match docs 2023-03-27 09:41:42 -07:00
Wez Furlong a0015424e4 add /opt/kumomta/policy to the default require path 2023-03-14 18:59:43 -07:00
Wez Furlong 8989586c9b refactor: move version info to its own crate
Several crates depend on the config crate, which means that
several things needed to be rebuilt after committing/amending/syncing.

Separating it out reduces that impact.
2023-03-11 06:27:31 -07:00
Wez Furlong 50a123a0e3 Add version info to kumod 2023-03-08 17:38:15 -07:00
Wez Furlong 55d3343ac5 config: implement pooling of lua contexts
and export some counters so that we can see into their usage
2023-03-08 16:53:04 -07:00
Wez Furlong eaaddbb6b6 add redis client 2023-03-05 09:57:03 -07:00