Commit Graph
151 Commits
Author SHA1 Message Date
Wez Furlong 2bec97dbe1 ci: fixup some flakeyness in maildir_batch_452_b 2025-12-18 14:12:05 +00:00
Wez Furlong 1598adb8cc add explicit http auth integration test
So far this has been mostly implicitly covered by other tests.
Add an explicit test for directly testing http auth with a password.
2025-12-18 10:34:08 +00:00
Wez Furlong 5926112956 acct: add kumo.aaa.configure_acct_log
This function sets up the accounting log which records authentication
and authorization events to a local log file.

Add some test assertions that we're not seeing any failed authn/authz
in a number of integration tests.

Add explicit check for failed a request to kcli's streaming metrics
parser, which previously would ignore the request status.
2025-12-18 08:22:31 +00:00
4e767ff96f Add support for proxy protocol (#440)
Add support for proxy_protocol for start_esmtp_listener

Co-authored-by: Solvik Blum <solvik@solvik.fr>
Co-authored-by: Wez Furlong <wez@wezfurlong.org>
Co-authored-by: ncai <ncai@chapsvision.com>
2025-11-13 07:54:36 -07:00
Wez Furlong 1c3b6dc14a smtp_dispatcher: fix reverse order for mx_list
When using an explicit mx_list, rather than consuming the DNS-provided
MX records, we were populating the connection plan LIFO with the mx_list
which is specified as a FIFO.

The result was that we would connect to the addresses in the reverse of
the intended order.

Most users only populate a single entry for mx_list, if they use this at
all, so it has flown under the radar.  This issue was introduced
together with the mx_list feature in bcd2946c53.

This commit corrects that issue so that the behavior conforms to the
intended and documented ordering, and tweaks the tests to accommodate
it; the test changes required were:

1. disconnect_in_data was actually sensitive to the order, and encoded
   the reverse ordering in its expectations. :-/
   It now correctly encodes the correct order, which should allow
   detection of regression in the future.

2. disconnect_terminate_ok is sensitive to extraneous TransientFailures.
   It was implicitly dependent on the ordering of the mx_list in the
   sense that it relied on the FIFO list returning as [127.0.0.1, ::1],
   which resulted in trying ::1 first (because that list is consumed as
   LIFO), failing due to ::1 not actually being bound in the test
   environment, and proceeding to test with 127.0.0.1 being the last
   working address.

   With the ordering fixed that test started to fail because the second
   host in the correct connection plan was ::1 and that was not
   reachable, triggering a TransientFailure against the expectations
   of the test.

   The goal of the test was to verify the behavior in the
   exhausted-connection-plan case, rather than the
   unreachable-final-mx-case, so the resolution here is to add v6
   addresses to the skip_hosts config.  Will that come back to haunt us?
   Time will tell.
2025-11-13 06:12:38 +00:00
Wez Furlong 1795fa6f56 message: ensure all data-loading methods are async and load data
This commit tidies up a bit of an API wart that stems from
the initial implementation where we couldn't guarantee that
the message crate would know how to get a spool handle to
implicitly load data when needed.

This has resulted in a couple of issues over time where we try to
operate on the message data and it just hasn't been loaded, leading to
transient errors being raised in the best case, but in a possible worst
case, a silent error condition.

During the rebind_message event, if the message is not loaded,
msg:get_data() could return the placeholder empty data value.  If the
rebind_message event is trying to mutate the message then the best case
is that an error is raised, a medium-bad case is that operations like
`msg:append_header` will fail to find the header block and thus fail,
and the worst case is that mutation operations that blinding prepend
data (perhaps msg:prepend_header) could end up assigning a bogus
truncated message.  While we don't generally recommend this sort of
action, this is potentially a very gnarly consequence of trying things
out.

Let's just head all of these sorts of issues off by making the accessors
async and have them internally load the data as required.
2025-11-11 10:28:51 +00:00
Wez Furlong 7ac9a5eac0 smtp_dispatcher: handle pathologic all-452 case
Continuing in the same vein as 94628f06ab,
ensure that we have a reasonable outcome when a batch of size > 1
encounters 100% ambiguous 452 responses.

Without special consideration we'd end up in an infinite retry loop for
these, as we'd think that we'd hit the recpient limit on the connection
every time.

This commit introduces a small amount of state into the dispatcher so
that we can remember the set of (spoolid, recipient) from the prior
transaction on the session.

When deciding whether something is eligible for immediate retry
we will gate that choice on the current (spoolid, recipient) being
absent from the prior set of ids.  In this way, we bound the immediate
retry for a recipient to one per connection.

To make things a bit more robust when trying to decide whether a 452
means "too many recipients" or not, if we're not sure, we now call out
to the bounce classifier which can be configured to emit a
`TooManyRecipients` classification.  If the classifier produces either
that or some non-Uncategorized response, then we have a confident
assessment.  Otherwise, we're unsure and will factor that into the
retry strategy.
2025-11-07 08:13:36 +00:00
Wez Furlong 94628f06ab smtp_dispatcher: fix interpretation of 452 for single-recip messages
The multi-recipient send logic introduced a regression in `main` that
could cause messages like "452 4.1.1 mailbox full" to be considered
immediately retryable always, because we thought it meant "too many
recipients" in this batch.

This commit fixes up the immediate retry logic to only apply when we
have a batch size > 1, and we try to be a bit more discerning with the
enhanced status code when it is present.

This issue isn't being mentioned in the changelog as a breaking change,
as the breakage is vs. an earlier `dev` build and not a prior stable
release.
2025-11-06 07:58:27 +00:00
Wez Furlong 5ad7df0203 arc: expose arc_seal and arc_verify methods to lua
refs: https://github.com/KumoCorp/kumomta/issues/16
2025-10-31 12:16:32 +00:00
Wez Furlong 0e82d3eda7 ci: hopefully address flakeyness in xclient tests 2025-10-29 13:06:48 +00:00
Wez Furlong 1d70c2ffbb new smtp_server_rewrite_response event
This allows you to customize or otherwise adjust the responses
made by the smtp server.
2025-10-29 08:54:10 +00:00
Wez Furlong 6fc0d782ff spf: populate smtp.mailfrom in Auth-Results, add check_msg func
Add a convenience function that is intended to be used around the
time that you might be building up the overall set of
Authentication-Results.

Populate missing smtp.mailfrom in SPF authentication result.
2025-10-21 21:25:21 +01:00
Wez Furlong 43e45e12c0 smtp_server: fix max_line_length issue
Partially reverts aac842439f and fixes the
underlying issue that led me to throw that together: the issue was that
the check_line_lengths function needed to add 2 to the index otherwise
iterations after the first would over-estimate the length of the
currently found line by 2, because the prior index included the CRLF
that it found in the input.

While poking at this, I found that there was a fairly harmless edge case
around non-DATA line handling: we used a 1024 stack buffer to read the
command in, and would accept anything that fit into that as valid, even
though the limit is set to a slightly smaller value.
2025-10-14 08:15:52 +01:00
Wez Furlong adfdcef1f8 spf: improve test coverage, couple of fixes
* Add exp= test based on an example in the rfc
 * Discovered that we were a bit too strict and didn't allow spaces
   as shown in that example
 * Add integration test for the ehlo_domain string quoting issue
   resolved in 236afa417f
 * lua spf check: Assume ehlo_domain = domain when sender is none
2025-10-13 08:22:36 +01:00
Wez Furlong 24602cff44 ci: reduce flakeyness in xclient test
I noticed a test failure where we hadn't yet received all of the
trace events that we wanted to inspect, so this commit introduces
the ability to wait for matching records to appear in the collected
set of data.
2025-10-09 09:12:41 +01:00
Wez Furlong 8fc11713d8 deps: update axum and utoipa ecosystem crates
A "casualty" of this is that the client-ip crate, which we were using
for its InsecureClientIp type for informational purposes, has retired
the InsecureClientIp type because it wasn't trustworthy overall
(https://github.com/imbolc/axum-client-ip/issues/32).

What we do for the time being is configure that crate to use the direct
peer IP.  There isn't a way to automagically select the "best"
information available without building in some kind of middleware, and
we probably should make that configurable in order to be fully
trustworthy.

So for now, we're punting on that until someone shows up with some
requirements and sponsorship and we'll make it happen then.
2025-10-08 10:24:57 +01:00
Wez Furlong af8f61d06e ci: fixup flakey maildir_batch test
need to wait for all 5 messages to land in the maildir before
we stop the services
2025-10-07 15:32:55 +01:00
Wez Furlong 7f4a929ca2 message transfer (xfer) support
This commit implements a kumomta-specific message transfer
protocol that is intended to be used to migrate messages
from one kumomta node to another.

The transfer is carried out using an HTTP POST request
to the destination node's http listener.

The request includes the full message metadata and body,
in a compressed form.

An xfer request can be made via `kcli xfer` (and thus also via an HTTP API
endpoint).  It works similarly to a rebind operation; you specify the
criteria to be used to match scheduled queues, along with the target
node for the xfer, and kumomta will find matching queues, drain out the
messages, make an adjustment to the metadata to capture current
scheduling information, and then place the messages into an xfer queue.

The xfer queue has hard-coded scheduling queue configuration of its own,
with the base retry interval set to 10 seconds, which should be suitably
aggressive for the intended use case.

You may apply shaping to affect the number of concurrent requests in a
similar way to how TSA shaping is configured.

On the receiving side, the incoming xfer sanity check to prohibit
trying to xfer to itself.

The spool id of the Message is not suitable to be reused verbatim on
another node (spool ids include the local mac address and creation
timestamp information, as well as a random component), so the receiving
side will derive an id that should be suitable for use on that node.
The originating node id and spool id will be preserved in metadata to
aid in tracing.

It is possible for an xfer request to target an existing xfer queue, so
that you can correct/update the target in various circumstances. In that
situation the messages will be "simply" moved from the source queue to
the destination queue.

It is possible to cancel an xfer request via `kcli xfer-cancel` (and
thus also via an HTTP API endpoint).  You specify the target queue,
which must be an xfer queue, and it will have its messages drained and
the metadata changes that were applied when the xfer was initiated will
be reversed, allowing the messages to then be reinserted into their
originating queue.

refs: https://github.com/KumoCorp/kumomta/issues/311
2025-10-07 10:58:07 +01:00
Wez Furlong 794113f0b0 smtp_dispatcher: add max_recipients_per_batch
This sets a client side shaping control to manage the maximum number of
RCPT TO commands issued in a single transaction.
2025-10-07 07:53:15 +01:00
Wez Furlong f968935315 add smtp_server_split_transaction event
This provides full control over how an incoming smtp transaction
will be split into batches.
2025-10-07 07:53:15 +01:00
Wez Furlong 18e55a6e91 smtp: handle "too many recipients" status more appropriately
This commit adds a test for exceeding the number of recipients
allowed in a transaction and fleshes out our handling of that
status so that we will immediately try the remaining recipients
of the batch, without delaying the message by eg: 20 minutes
in the default retry parameters.

As part of this, I noticed that we were returning the wrong
smtp status code in our server implementation, so this
commit fixes that up.
2025-10-07 07:52:58 +01:00
Wez Furlong 888928b60f WIP: basic batch relaying and end-to-end test
This makes a first pass over relaying a batch, with a test
to prove that it worked.

The logic in the smtp client likely needs to be enhanced
to handle hitting recipient-per-transaction limits; tests
for various scenarios need to be added in follow-on commits.
2025-10-07 07:52:57 +01:00
Wez Furlong 4255e02ffa WIP: smtp_server: scaffolding to support for batching by domain
This adds a `batch_handling` parameter that optionally allows splitting
up a recipient batch based on the destination domain.

The system does not yet have any support for correctly delivering such a
batch.
2025-10-07 07:52:57 +01:00
Wez Furlong 9079a9e569 WIP: more multi-recipient type changes
This commit updates the type signatures of some key structures
to allow for the possibility of a Message having multiple recipients:

 * Message (continuing from previous commit)
 * JsonLogRecord
 * MessageInformation

Some logic has been updated to account for multiple recipients,
but critically, nothing in the core will generate them, and none
of the queue management or delivery infrastructure is aware
of recipients beyond the first.
2025-10-07 07:52:57 +01:00
Wez Furlong 53a92c5cda ci: try to reduce flakiness for the lapin integration tests
This seems to flake out reasonably regularly in CI on PRs.
Let's see if adjusting the ordering of things a little bit
encourages a less flakey result.
2025-10-06 20:12:19 +01:00
Wez Furlong 5228f794bb xclient: capture original via/from address in context
I can see someone wanting to know the raw physical connection
info at some point, regardless of the adjusted xclient values,
or in the future, the proxy protocol adjusted values.
2025-10-03 20:03:14 +01:00
Wez Furlong d9d3d14e3e enable XCLIENT support
Hooks up the parsed XCLIENT command to the smtp server.
We support just the address changing attributes; we don't
do anything special with the NAME attribute, and I'd like
to think a lot more about authentication before allowing
messing with the LOGIN attribute.

After applying changes to the metadata in the connection,
take care to re-evaluate the listener parameters and
dynamic parameters so that `via` and `peer` blocks can
match the new configuration.
2025-09-26 09:36:15 +01:00
Wez Furlong 90dd172f6c wrap up 8BITMIME and SMTPUTF8 support
Finishes up support for these extensions by adding a send-time
check and error for the case where we have 8 bit content and
the appropriate extension is not advertised by the next hop.

A shaping option is provided to bypass this, both to facilitate
testing and as a way to bypass it for situations where the
operator knows best.

closes: https://github.com/KumoCorp/kumomta/issues/327
2025-09-25 12:37:11 +01:00
Wez Furlong c86b8325a6 ndr: don't try to generate one for the null sender 2025-09-25 07:18:46 +01:00
Wez Furlong 7fffccf410 Add support for generating NDRs
This commit adds some plumbing to facilitate generation of RFC 3464
non-delivery reports and some corresponding glue to enable calling
it from lua.

Examples in the docs that are also added.
2025-09-09 14:12:52 +01:00
Wez Furlong a478a0675d integration-tests: add helper for constructing tests
Makes it a bit more scalable to configure DaemonWithMaildir in tests
2025-09-09 10:39:51 +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
Daniel Schaaff 25a292a82e add support for compressing http body (#394)
* implement support for compression in common http server code

Adds a decompression layer to enable receipt and automatic decompression of compressed request bodies.

Signed-off-by: Daniel Schaaff <daniel@danielschaaff.com>
2025-07-25 05:56:12 +01:00
Wez Furlong 9e62a275c5 add tls_required_client_ca esmtp listener parameter
This enables configuring mTLS support in the listener.

Adjust TLS client tests to use this on the sink side and verify
that mTLS works for both inbound and outbound SMTP.

refs: #100
refs: #391
2025-07-24 15:40:49 +01:00
Wez Furlong a2fb749088 tls_client_certificate: fix performance, assert tls info
Fix the performance of the success test cases (they would wait 50s for a
TransientFailure that would never occur).

Add assertion for new server tls info being set and recorded in the
Reception log records on the sink side.

refs: #100
refs: #391
2025-07-24 13:14:25 +01:00
Wez Furlong c8027effb6 smtp_server: collect incoming tls information for logs/meta
This commit extracts the TLS version, cipher and subject name from
the TLS state and captures it:

* In the trace headers
* In the connection metadata
* In the Reception log record

In order to capture the info in trace headers, this commit will
now change the reception protocol to ESMTPS (for SSL) or ESMTPSA (for
authenticated SSL), and emit the version and cipher information as
a comment like: `with ESMTPS (TLSv1_3:TLS13_AES_256_GCM_SHA384)`.

closes: #100
2025-07-24 13:01:13 +01:00
kayozaki 76add2ced5 Adding support for presenting Client cert as part of make_egress_path (#391) 2025-07-24 13:00:27 +01:00
Wez Furlong fa5d6a6e7a allow specify port number in queue and routing_domain meta
The motivating use case here is to allow using a rebind command to
specify `smarthost:port`.  You can achieve that via an invocation
like:

```
kcli rebind --everything --set queue=smarthost:port
```

or:

```
kcli rebind --everything --set routing_domain=smarthost:port
```

refs: https://github.com/KumoCorp/kumomta/issues/352
2025-07-21 15:38:28 +01:00
Wez Furlong c914f9b045 dns-resolver & EnvelopeAddress: explicitly check and deny ports in domain
This is prep work ahead of https://github.com/KumoCorp/kumomta/issues/352

This commit adds an explicit check for port numbers being passed in
via the domain when setting envelope addresses.

This is forbidden by the SMTP spec. This commit adds an explicit
integration test to confirm that we're reject them there.

For HTTP injection we don't have any other central point to
validate addresses, so this commit expands on the EnvelopeAddress::parse
method to add that centralized check.

dns-resolver's domain name validation is refactored into a domain
name classifier to help reuse that same logic outside the context
of making an actual DNS lookup.

MailExchanger::resolve is now also explicitly checking for and denying
lookups that include a port number.

This commit should have no practical functional impact; all of the
things denied by these checks are illegal in SMTP.
2025-07-21 12:30:29 +01:00
Wez Furlong d9a2392ccb allow setting destination smtp port via mx_list 2025-07-21 12:30:29 +01:00
Mike Hillyer 58e9770206 We miss you Mister Rogers. 2025-07-18 12:11:14 -04:00
Wez Furlong b0c6b1cf6a smtp_dispatcher: retry more aggressively in transport-error-ish scenarios
Some sites are non-conforming in their behavior when they have a policy
that issues a transient failure and then snips the connection without
explicitly issuing a 421.

This can cause "splash damage" where a message is transiently failed
and the connection snipped "disgracefully" and a subsequent message
that is due to go out on that same established connection encounters
a transport error when the RSET part of the pipeline is sent.

This commit provides methods on the ClientError and Response types
to help reason about whether the response might be specifically
about the message being delivered, vs. more of a transport error.

The gist of this check is that timeouts and transport level stuff
where there is no protocol level response are not considered to
be due to the message.  Protocol level responses prior to MAIL FROM
are similarly considered to be more like transport errors.

The functional change in this commit is:

* If we get a transport-ish error and we have more candidate hosts
  available in the connection plan, then after logging a
  TransientFailure for the error we just encountered, we'll push
  back the message to be tried again on the next connection that
  we open in the current session.

* For timeout errors specifically, this behavior is gated behind
  a try_next_host_on_timeout boolean option.
2025-05-08 10:41:06 -07:00
Wez Furlong fedd2959b6 enhance log_arf, log_oob to support LogThenDrop
It is common to want to express that incoming reports should
be logged, but not relayed.

This commit facilitates that by expanding `log_arf` and `log_oob`
to allow more than just a simple boolean value.

We now support both LogThenRelay and LogThenDrop as dispositions
for an incoming report, as well as Ignore to not care about the
report content.

For the LogThenDrop case we'll log only the incoming OOB or Feedback
record to the logs and then discard the incoming message.

For LogThenRelay we'll log the incoming OOB/Feedback report as well
as enqueue the message and log a Reception and, subsequently, records
for its delivery attempts.

The previously supported boolean values are mapped to `Ignore` (false)
and `LogThenRelay` (true), respectively: their behavior is the same as
in prior releases.

The integration test and smtp server tracing infra has been enhanced a
bit to support extracting these new disposition states from the tracer
during the integration test.
2025-05-05 08:25:40 -07:00
Wez Furlong 4b83db6b6a integration-tests: add wait_for_sink_summary helper 2025-04-23 09:14:39 -07:00
Wez Furlong 159e9ddd94 integration-tests: wait longer for tsa and kumod to shutdown 2025-04-23 09:14:24 -07:00
Wez Furlong 02d7c8fcbe accounting: add sqlite busy timeout
This helps to tackle some flakiness in the integration
test environment.
2025-04-22 10:24:31 -07:00
Wez Furlong e3fdcaa628 cargo clippy --fix 2025-04-09 10:26:07 -07:00
Wez Furlong c2444b6266 add expiry to per-msg scheduling overrides
This commit allows setting a per-message `expires` timestamp
via msg:set_scheduling (and thus msg:import_scheduling_header).

The expiration takes precedence over max_age; max_age will be
ignored for messages that have configured and expiration time.

The expiration time is independent of the other scheduling
restrictions.
2025-03-15 07:18:13 -07:00
Wez Furlong 1fac830f94 integration-tests: asyncify disposition log gathering
Prior to this commit, disposition log gathering used blocking code to
consume the logs.

This was a surprising source of issues for the kumod under test: because
the disposition log reading code was blocking, and our higher level
logic to wait for a condition to become true was built on top of that,
the disposition log collection would block the stderr/stderr reader
tasks that take the kumod process output and emit it to our own stderr.

Given sufficient diagnostic (rather than disposition) log volume, pipe
buffers could fill up and cause the kumod process under test to block.

This could in turn lead to a surprising soft deadlock between the kumod
process trying to make forward progress and being blocked by its full
stderr pipe, and the integration test process that is waiting to
read disposition logs until it can get back to process the pipe reads
from the kumod process.

This commit uses spawn_blocking to put the blocking parts of the
integration test harness into a suitable background context so that
the kumod log output can continue unhindered and resolve this
issue.
2025-03-15 07:18:12 -07:00
Wez Furlong 0e339a2b9e egress_source: add integration tests and fixup source_selection_rate
Add test coverage for source_selection_rate and tidy up how we reflect
being throttled due to source_selection_rate; in the first cut
we made it look like the ready queue was full, but now we're a
bit more deliberate:

* If any of the candidate queues were full, we report that the
  ready queue was full so that the message can be retried again
  "soon".
* If we hit a source_selection_rate throttle, we use the throttle
  delay to adjust the min delay period and if we run out of candidate
  sources, we'll have the same sort of return value as we would
  for a suspension with a definite duration; the message will
  log that transient failure in the same way with the same sort
  of retry schedule.  I've revised the wording in that case to be
  "no sources for SITE pool=`POOL` are eligible for selection at this time"
  (the important part being `at this time`) to suggest at the
  transient nature of the unavailability.
* If we exhaust all sources for other reasons, we'll treat this
  as the NoSources case. In practice, this should mean that the
  source has zero weight. We'll now report "no sources available for SITE
  pool=`POOL`" in this case instead of the more precise but wordy
  "no non-zero-weighted sources available for SITE" that we used
  prior to this commit.
2025-03-11 08:37:09 -07:00