Commit Graph
3746 Commits
Author SHA1 Message Date
Wez Furlong 3174bd5a39 rustfmt again 2026-06-23 11:24:04 +01:00
Wez Furlong a1a7e16759 docs: fixup links 2026-06-23 10:13:11 +01:00
Wez Furlong 2a657f68fb included scheduled queue config in resolve-egress-path output
and also in resolve-shaping-domain, for consistency.

We can use the message rate from the scheduled queue config to refine
the computed ceilings/constraints too, which is nice.
2026-06-23 10:00:07 +01:00
Wez Furlong 49e1a62733 add resolve-egress-path API endpoint
This is conceptually equivalent to resolve-shaping-domain but operates
on a live instance, rather than loading up the policy offline.
2026-06-23 10:00:05 +01:00
Wez Furlong 1382610787 rustfmt 2026-06-23 09:59:34 +01:00
Wez Furlong 61bb95a558 resolve-shaping-domain: expand output
This changes the output to be a bit more human friendly by default
by rendering as toml.

It also includes the rate ceilings added in the prior commit.
2026-06-23 09:59:32 +01:00
Wez Furlong 0a72ce7f02 introduce EgressPathConfigConstraints concept
This can be computed for an EgressPathConfig and is useful to indicate
what the effective ceilings are for this egress path.

We return this from inspect-ready-q
2026-06-23 09:58:38 +01:00
Wez Furlong b9e677dd16 add abort-ready-q-conn and inspect-readyq endpoints/commands
These build on the infrastructure from the prior commit and improve
the observability of the system.

refs: https://github.com/KumoCorp/kumomta/issues/539
2026-06-23 09:58:36 +01:00
Wez Furlong 423abc4704 add per-dispatcher state tracking and watchdog
The ready queue maintainer now tracks for each dispatcher whether it
is making progress.  If it exceeds dispatcher_progress_watchdog_timeout
then the task will be aborted and any message(s) it held onto will
be returned to the scheduled queue.  This will free up the connection
slot and allow another connection to be attempted, potentially
making further progress.

refs: https://github.com/KumoCorp/kumomta/issues/539
2026-06-23 09:56:45 +01:00
Wez Furlong 1b726e4db2 rfc5321: add timeouts to flush and starttls handshakes
These are speculative defensive changes made in response to
refs: https://github.com/KumoCorp/kumomta/issues/539
I do not believe that either of these fix the underlying issue,
because we don't have enough data to know for sure, and the
reported behavior doesn't quite match up.

Two things:

1. While the STARTTLS verb itself respected the client timeouts,
   the underlying handshake was issued without a surrounding handshake,
   which meant that if something bad happened mid handshake and we
   got stalled, we'd stall indefinitely.  It's not clear that this
   is the root cause of the issue above, as the user reported that
   they've seen sessions hang after successfully being established.

2. Similarly, we'd use the client timeouts for other verbs, but the
   flushes we issue were not included within their scope.  So again
   we could potentially wedge here with no timeout.  It's not clear
   that this is the root cause either, because analsis of the code
   and building out a mock wedging implementation (not included in
   the final commit here) couldn't trigger a situation that manifested
   the way the issue described.
2026-06-23 09:55:21 +01:00
Wez Furlong 75c88b23ff rustfmt 2026-06-23 09:24:25 +01:00
Wez Furlong 002d6593f0 new: suspend_when_unplumbed and suspend_when_proxy_unhealthy source options
Egress sources can now be configured to auto-suspend when their local
bind address appears unplumbed or when their configured proxy server
appears unreachable.  A suspended source is skipped during pool selection
until the configured duration elapses.
2026-06-23 09:22:27 +01:00
Wez Furlong e21bb1c672 ha_proxy_server and socks5_proxy_server now also resolve host names
We'll expand these to a list of candidate addresses, trying each in
turn.  We use the same overall connect timeout regardless of how
many candidates are present, to respect that timeout bound.

closes: https://github.com/KumoCorp/kumomta/pull/402
2026-06-23 09:11:53 +01:00
Wez Furlong 99b41d8e4c introduce ResolvableSocketAddr type
This is essentially a SocketAddr that can specify a hostname
in addition to literal IP addresses.  A port is required.

refs: https://github.com/KumoCorp/kumomta/pull/402
2026-06-23 09:11:53 +01:00
Wez Furlong 9987248313 spool: surface and handle rocksdb background errors
We recently investigated an issue where a rocksdb had been damaged by
corrupting/removing SST files (it sounded like this was accidentally
self-inflicted by some backup/orchestration infrastructure) leaving the
system in a silently-broken state: writes just wouldn't make progress
and there were no error messages.

Inspecting the `/var/spool/kumomta/data/LOG` log file (which is a
readable text file) revealed messages like:

```
2026/06/12-14:55:31.884227 2875746 [ERROR] [db/compaction/compaction.cc:262] Unable to load table properties for file 29704 --- IO error: No such file or directory: While open a file for random read: /var/spool/kumomta/data/029704.sst: No such file or directory
2026/06/12-14:55:31.884311 2875746 [ERROR] [db/db_impl/db_impl_compaction_flush.cc:3385] Waiting after background compaction error: IO error: No such file or directory: While open a file for random read: /var/spool/kumomta/data/029704.sst: No such file or directory, Accumulated background error counts: 6363
```

This commit improves the observability in this situation by proactively
checking for error conditions:

1. The store() and remove() operations now use our own polling within
   a deadline loop rather than spawning a blocking task and delegating
   to rocksdb's blocking interface.  This allows us to inspect the
   background error count and be cancellable, safely respecting and
   caller provided smtp max transaction duration.

2. All read and write operations check for IO and Corruption errors
   and immediately latch an error state

3. The metrics monitoring task inspects and track background error
   counts and latch us into an unhealthy state when the background
   error state appears unhealthy and persistent.

4. Additional metrics are exposed to help monitoring and alerting

While adding integration test coverage for this, I found a typo that
meant that spool errors were ignored in the message crate; they got
silently converted to `true` in all cases rather than just mapping
the success case to a `true`.

Integration tests handle the case where an SST file is corrupted
(truncated) during runtime, as well as starting up when an SST file
is missing.   These excercise both the foreground and background
error detection paths.
2026-06-23 09:11:48 +01:00
Wez Furlong 9c8183a433 docs: update for release 2026.06.23-f3af1cd0 2026-06-23 09:02:10 +01:00
Wez Furlong af6356419a find-since-dev: handle no-matching since('dev') case 2026-06-23 08:59:14 +01:00
Wez Furlong f3af1cd000 deps: ./assets/ci/update-cargo-deps-with-cooldown.sh 2026-06-23 07:30:53 +01:00
Wez Furlong 77b1db8154 docs: fix proxy listener doc build
This appears to have been accidentally committed in 1999ba0251
2026-06-23 07:23:37 +01:00
Wez Furlong 1632d4a922 fix test flake in mx_list_refresh
Resolve this by waiting for the metrics to hit an expected value
before we start the second round of injection.
2026-06-18 17:43:51 +01:00
Wez Furlong e07f863029 deps: ./assets/ci/update-cargo-deps-with-cooldown.sh 2026-06-17 23:03:56 +01:00
Wez Furlong 40562f7824 deps: add cargo cooldown config 2026-06-17 22:55:32 +01:00
Wez Furlong d88fee2e0d docs: manually run cargo run -p jsonschematodocs
This somehow got missed in an earlier commit
2026-06-17 22:52:45 +01:00
Mike Hillyer d3d4a4d7c2 Final shuffle and updates prior to publish. 2026-06-11 15:25:53 -04:00
Mike Hillyer 1999ba0251 Continue traffic shaping chapter refactor. 2026-06-09 13:23:56 -04:00
Mike Hillyer 164f109326 Traffic Shaping Refactor First Pass 2026-06-09 13:23:56 -04:00
Wez Furlong 4eedf9361e fix punycode normalization for STARTTLS
I believe this to be a relatively recent regression since we started
to preserve the incoming domain name in RCPT TO, rather than
normalizing it. (2026.04.09-ea3b2a9b)

refs: https://github.com/KumoCorp/kumomta/issues/533
2026-05-30 17:35:28 +01:00
Harsh JhaandWez Furlong 00f3131f7d Allow configuring which records TSA skips
Closes: https://github.com/KumoCorp/kumomta/pull/525

Co-authored-by: Wez Furlong <wez@wezfurlong.org>
2026-05-29 15:02:31 +01:00
Wez Furlong c7750deada fix flakey mx_list_refresh test 2026-05-29 11:07:19 +01:00
Aditya GantiandWez Furlong 7bfbbe720a fix(mod-aws-sigv4): apply Trimall, require host header, fix S3 content-sha256
Co-authored-by: Wez Furlong <wez@wezfurlong.org>
Closes: https://github.com/KumoCorp/kumomta/pull/522
2026-05-29 10:16:06 +01:00
Wez Furlong 5fb9dd104d fix regression in serializing original_message in ARF
This got overlooked around the time that 7518ca6de was pushed.
The intent was to serialize as a string when possible, bytes otherwise.
2026-05-29 09:45:22 +01:00
Wez Furlong 06a26f4ba6 kumo-jsonl: make tailer more tolerant of errors
Two main cases:

 * If the user accidentally places files in the directory that
   are not kumo-jsonl compatible segments, then we won't trip
   over them and stop processing.

 * If the log segments are incomplete (eg: kumod was SIGKILL'd)
   then we log the issue and avance to the next segment
2026-05-29 08:57:47 +01:00
Wez Furlong 5a7aa96cf1 docs: update for 2026.05.12-a6845223 release 2026-05-27 06:47:55 +01:00
Wez Furlong a684522394 rustfmt 2026-05-12 16:50:01 +01:00
Wez Furlong 62de291279 docs: suggest folks prefix their metadata names
Should help to avoid possible conflicts with the core metadata names.
2026-05-12 14:35:16 +01:00
Harsh JhaandWez Furlong 7cba56bc06 http injection: allow static per-recipient metadata
That metadata is accessible in to via msg:get_meta('extra')

closes: https://github.com/KumoCorp/kumomta/pull/516
Co-authored-by: Wez Furlong <wez@wezfurlong.org>
2026-05-12 14:25:46 +01:00
kay ozakiandWez Furlong 1febfcc55e lua counter series using kumo-counter-series
Co-authored-by: Wez Furlong <wez@wezfurlong.org>
closes: https://github.com/KumoCorp/kumomta/pull/507
2026-05-07 15:35:17 +01:00
Wez Furlong 2c0e9982e1 docs: fmt examples in import_headers.md 2026-05-07 14:25:34 +01:00
Wez Furlong 421bff1a41 docs: changelog for #504 2026-05-07 14:21:01 +01:00
Oleg Orekhov c237e16c5f mod-http: allow disabling certificate verification on the client
closes: https://github.com/KumoCorp/kumomta/pull/504
2026-05-07 14:18:09 +01:00
Wez Furlong fcb0981c31 deps: update rust-openssl 2026-05-06 22:50:49 +01:00
Wez Furlong 421b8a289e proxy: keepalive will work on macos and other unix too
So remove the linux only gate
2026-05-06 22:38:50 +01:00
dependabot[bot] d7781ca588 build(deps): bump mozilla-actions/sccache-action from 0.0.9 to 0.0.10 (#514)
Bumps [mozilla-actions/sccache-action](https://github.com/mozilla-actions/sccache-action) from 0.0.9 to 0.0.10.
- [Release notes](https://github.com/mozilla-actions/sccache-action/releases)
- [Commits](https://github.com/mozilla-actions/sccache-action/compare/v0.0.9...v0.0.10)

---
updated-dependencies:
- dependency-name: mozilla-actions/sccache-action
  dependency-version: 0.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-06 21:56:42 +01:00
Wez Furlong ab4a47a016 ci: docker: only tag :latest on release tag pushes
The docker/metadata-action config relied on the default flavor
(latest=auto), which adds :latest when the ref is the default branch
or a SemVer git tag. Our release tags are date-based (e.g.
2026.03.04-bb93ecb1) and are not SemVer, so the SemVer rule never
fired and every push to main re-pointed :latest at the dev build.
The inline "produce latest tag when tagging" comment on
type=ref,event=tag was misleading — that entry only emits a tag
matching the git tag name, it does not produce :latest.

Set flavor: latest=false to disable the implicit behavior, and add
an explicit type=raw,value=latest entry gated on github.ref_type ==
'tag' so :latest moves only on release tag pushes.

refs: https://github.com/KumoCorp/kumomta/issues/511
2026-05-06 21:41:33 +01:00
Wez Furlong b0fa4275f3 cargo update
Note: need to pin hierarchical_hash_wheel_timer back because the latest
version requires a rustc upgrade, and current versions of mlua don't
build against it when the send feature is enabled.
2026-05-06 21:32:01 +01:00
dependabot[bot] bd88a3678d build(deps): bump docker/metadata-action from 5 to 6 (#494)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-06 21:15:53 +01:00
JackandWez Furlong c4dbaa743b proxy-server: add tcp_keepalive option
Some upstream peers (e.g. QQ Mail's rate-limiter) silently hold a
proxied TCP connection open indefinitely — sending no data, FIN, or RST
— rather than cleanly refusing.  Without some kind of timeout
management, the two file descriptors for such a session remain open for
the lifetime of the process, slowly exhausting the kernel's
file-descriptor table and occupying proxy-server worker slots.

This commit configures kernel level keepalive options with reasonable
defaults to detect and close out this class of connection.

Co-authored-by: Wez Furlong <wez@wezfurlong.org>

Closes: https://github.com/KumoCorp/kumomta/pull/509
2026-05-06 21:09:10 +01:00
Wez Furlong ee1e5b980e message: add msg:import_headers, a flexible alternative to import_x_headers
import_headers takes an array of per-spec option tables, each describing
how a single header name or pattern should be imported into the message
metadata. Compared to import_x_headers it adds:

 * Trailing-`*` wildcard patterns (e.g. `X-*`) alongside exact names.
   Bare/leading/interior wildcards are rejected at compile time.
 * `match` of `first`, `last` (default), or `all`. `all` captures every
   matching header instance as an array of strings; the others capture
   a string. Specs that produce no matches write nothing.
 * `transform` selects the metadata key style: `snake_case` (default,
   matches the existing import_x_headers behavior), `kebab_case`,
   `camel_case`, or `pascal_case`. Header matching itself is always
   case-insensitive.
 * `target` overrides the metadata key for exact-name specs.
 * `remove` strips the matched headers from the message body in a
   single follow-up pass.

When more than one spec could match a header, the first matching spec
wins, so callers can place specific rules ahead of a wildcard catch-all.

import_x_headers now delegates to import_headers, so its behavior is
unchanged and the two share a single implementation.

retain_headers now passes the header index alongside the &Header to its
closure, which import_headers uses for its post-pass removal step
instead of tracking a parallel counter. Existing callers that don't
need the index ignore it with `_`.

Closes: #515
2026-05-06 16:30:36 +01:00
kay ozakiandWez Furlong f2d98eaf62 psl: normalize domains before psl::domain_str / suffix_str lookups
The `psl` crate's domain_str / suffix_str do byte-exact lookups against
the public-suffix list, so uppercase or trailing-dot inputs (e.g.
"Example.COM" or "example.com." from a DNS Name) silently return None.
Several call sites were passing through user- or DNS-supplied domains
unmodified.

Add a small psl-utils crate that wraps the psl crate:

* normalize_domain(&str) -> Cow<str>: strips a single trailing dot and
  lowercases ASCII; borrows when the input is already normalized so
  the hot path (alignment loop) doesn't allocate.
* domain_str / suffix_str: thin re-exports for callers that have
  already normalized.

psl-utils becomes the only workspace crate that directly depends on
the psl crate; kumo-dmarc and mod-string switch to depending on
psl-utils instead.

Updated call sites:

* kumo-dmarc record.rs: is_relaxed_aligned / is_strict_aligned now
  normalize both inputs first. Replaces the prior eq_ignore_ascii_case
  comparisons.
* kumo-dmarc lib.rs: the organizational-domain fallback in
  DmarcContext::check now normalizes from_domain before calling
  domain_str and compares against the normalized form. Previously a
  mixed-case From: header would skip the _dmarc.<org> lookup entirely.
* mod-string lib.rs: the Lua-exposed string.psl_domain and
  string.psl_suffix bindings normalize their input. Behavior change:
  inputs that previously returned nil due to case or trailing dot now
  resolve.

Co-authored-by: Wez Furlong <wez@wezfurlong.org>
Closes: https://github.com/KumoCorp/kumomta/pull/513
2026-05-06 13:34:15 +00:00
kay ozaki 20e26891b0 dmarc: fix alignment evaluation passing unauthenticated mail
Two bugs in the alignment logic added in the prior commit:

* auth_result_is_pass read auth_result.props["result"], but the
  pass/fail status lives in the AuthenticationResult.result field.
  Real callers don't populate a "result" key in props, so every DKIM
  and SPF entry was treated as non-pass and skipped. Take
  &AuthenticationResult and inspect .result directly.

* When no alignment succeeded and no per-method errors were recorded
  (e.g. no DKIM signatures, SPF not pass), evaluate() fell through to
  Disposition::Pass / "Success", silently passing unauthenticated
  mail. Return the published policy disposition with context
  "No aligned DKIM or SPF" instead.

Add regression tests dmarc_both_spf_and_dkim_fail_returns_fail (covers
the fall-through) and dmarc_dkim_relaxed_subdomain_reverse (relaxed
alignment when d= is a subdomain of From).
2026-05-06 13:29:15 +00:00