Commit Graph
438 Commits
Author SHA1 Message Date
Wez Furlong 6e52594de4 mailparsing: add Mailbox and MailboxList header parsing
For eg: the Sender and From headers.

This commit introduces a parser for RFC 5322 message header syntax,
as modified by RFC 2047 for encoded header fields.
2023-08-13 22:10:13 -07:00
Wez Furlong 41d7bf406f mailparsing: add HeaderMap 2023-08-13 22:10:12 -07:00
Wez Furlong 72e7118c46 mailparsing: refactor header parse result 2023-08-13 22:10:12 -07:00
Wez Furlong 4d945a7c52 add mailparsing crate
This kicks off our own mail parsing and building crate
2023-08-13 22:10:12 -07:00
Wez Furlong 5bea1013da trace_smtp_server: add colorized output
Makes it easier to see the sent vs. received data
2023-08-13 16:57:00 -07:00
Wez Furlong 4f53566fde trace_smtp_server: show meta keys one per line
This makes it easier to read and see what changed
2023-08-13 16:40:04 -07:00
Wez Furlong c3e6a4fce7 trace_smtp_server: add timestamp to trace events 2023-08-13 15:34:28 -07:00
Wez Furlong 81ff4043e3 add kcli trace-smtp-server subcommand
This allows tracing incoming SMTP sessions.

Sessions are traced through to the cli in realtime.

The implementation is via a websocket, which makes this also potentially
pluggable into a webui in the future, although care must be taken to
ensure that only authorized system operators can enable this, as it can
reveal authentication secrets in the traced dialog, as well as the
content of messages.

This commit doesn't include documentation on the shape of the websocket
JSON at this time.

refs: https://github.com/KumoCorp/kumomta/issues/44

```
; ./target/debug/kcli trace-smtp-server
[127.0.0.1:51778->127.0.0.1:2025] === Connected {"received_from":"127.0.0.1:51778","received_via":"127.0.0.1:2025","reception_protocol":"ESMTP"}
[127.0.0.1:51778->127.0.0.1:2025] <-  220 foo Welcome to KumoMTA!
[127.0.0.1:51778->127.0.0.1:2025]  -> EHLO foo.lan
[127.0.0.1:51778->127.0.0.1:2025] === smtp_server_ehlo: Ok
[127.0.0.1:51778->127.0.0.1:2025] <-  250-foo Aloha foo.lan
[127.0.0.1:51778->127.0.0.1:2025] <-  250-PIPELINING
[127.0.0.1:51778->127.0.0.1:2025] <-  250-ENHANCEDSTATUSCODES
[127.0.0.1:51778->127.0.0.1:2025] <-  250 STARTTLS
[127.0.0.1:51778->127.0.0.1:2025]  -> STARTTLS
[127.0.0.1:51778->127.0.0.1:2025] <-  220 Ready to Start TLS
[127.0.0.1:51778->127.0.0.1:2025]  -> EHLO foo.lan
[127.0.0.1:51778->127.0.0.1:2025] === smtp_server_ehlo: Ok
[127.0.0.1:51778->127.0.0.1:2025] <-  250-foo Aloha foo.lan
[127.0.0.1:51778->127.0.0.1:2025] <-  250-PIPELINING
[127.0.0.1:51778->127.0.0.1:2025] <-  250-ENHANCEDSTATUSCODES
[127.0.0.1:51778->127.0.0.1:2025] <-  250 AUTH PLAIN
[127.0.0.1:51778->127.0.0.1:2025]  -> AUTH PLAIN AHNjb3R0AHRpZ2Vy
[127.0.0.1:51778->127.0.0.1:2025] === smtp_server_auth_plain: Ok: Bool(true)
[127.0.0.1:51778->127.0.0.1:2025] <-  235 2.7.0 AUTH OK!
[127.0.0.1:51778->127.0.0.1:2025] === conn_meta updated to {"authn_id":"scott","authz_id":"scott","received_from":"127.0.0.1:51778","received_via":"127.0.0.1:2025","reception_protocol":"ESMTP"}
[127.0.0.1:51778->127.0.0.1:2025]  -> MAIL FROM:<wez@example.com>
[127.0.0.1:51778->127.0.0.1:2025] === smtp_server_mail_from: Ok
[127.0.0.1:51778->127.0.0.1:2025] <-  250 OK EnvelopeAddress(\"wez@example.com\")
[127.0.0.1:51778->127.0.0.1:2025]  -> RCPT TO:<wez@wezfurlong.org>
[127.0.0.1:51778->127.0.0.1:2025] === smtp_server_rcpt_to: Ok
[127.0.0.1:51778->127.0.0.1:2025] <-  250 OK EnvelopeAddress(\"wez@wezfurlong.org\")
[127.0.0.1:51778->127.0.0.1:2025]  -> DATA
[127.0.0.1:51778->127.0.0.1:2025] <-  354 Send body; end with CRLF.CRLF
[127.0.0.1:51778->127.0.0.1:2025]  -> Date: Sun, 13 Aug 2023 10:10:40 -0700
[127.0.0.1:51778->127.0.0.1:2025]  -> To: wez@wezfurlong.org
[127.0.0.1:51778->127.0.0.1:2025]  -> From: wez@example.com
[127.0.0.1:51778->127.0.0.1:2025]  -> Subject: test Sun, 13 Aug 2023 10:10:40 -0700
[127.0.0.1:51778->127.0.0.1:2025]  -> Message-Id: <20230813101040.1426417@foo>
[127.0.0.1:51778->127.0.0.1:2025]  -> X-Mailer: swaks v20201014.0 jetmore.org/john/code/swaks/
[127.0.0.1:51778->127.0.0.1:2025]  ->
[127.0.0.1:51778->127.0.0.1:2025]  -> This is a test mailing
[127.0.0.1:51778->127.0.0.1:2025]  ->
[127.0.0.1:51778->127.0.0.1:2025]  ->
[127.0.0.1:51778->127.0.0.1:2025]  -> .
[127.0.0.1:51778->127.0.0.1:2025] === smtp_server_message_received: Ok
[127.0.0.1:51778->127.0.0.1:2025] === Message from=wez@example.com to=wez@wezfurlong.org id=5465de7739fc11ee8af250ebf67f93bd
[127.0.0.1:51778->127.0.0.1:2025] === Message queue=wezfurlong.org relay=true log_arf=false log_oob=false
[127.0.0.1:51778->127.0.0.1:2025] === Message meta: {"authn_id":"scott","authz_id":"scott","received_from":"127.0.0.1:51778","received_via":"127.0.0.1:2025","reception_protocol":"ESMTP"}
[127.0.0.1:51778->127.0.0.1:2025] <-  250 OK ids=5465de7739fc11ee8af250ebf67f93bd
[127.0.0.1:51778->127.0.0.1:2025]  -> QUIT
[127.0.0.1:51778->127.0.0.1:2025] <-  221 So long, and thanks for all the fish!
[127.0.0.1:51778->127.0.0.1:2025] === Closed
```
2023-08-13 10:17:04 -07:00
Wez Furlong 55770052a5 add kumo.encode module
This provides a variety of encode/decode functions for things
like base64 and base32 encoding
2023-08-12 08:42:44 -07:00
Wez Furlong 99fcfbc64e kumod: refresh queue_config periodically
This commit causes the scheduled queue maintainer to refresh
the queue config by calling the get_queue_config event approximately
every minute while the queue is alive.

In addition, we now thread the routing_domain through to get_queue_config
2023-08-12 06:22:54 -07:00
Wez Furlong d8a6bceef0 add kumo.digest lua module
Compute hashes
2023-08-11 21:44:40 -07:00
Wez Furlong e042e81de9 kumo.dns: add lookup_addr function
Resolves a hostname to its v4 and v6 addresses and returns them.

Fixes up a typo in the docs for lookup_mx that incorrectly
showed it as resolve_mx.
2023-08-11 16:51:54 -07:00
Wez Furlong a6b79054b7 traffic-gen: rename --domain to --domain-suffix, add new --domain opt
You may now remove or replace the test domain suffix from the set
of senders/recipients by usingn `--domain-suffix ''` to remove
it, or some other value to set it to something else.

`--domain` can now be used multiple times to build out the list
of test domains.
2023-08-11 12:37:15 -07:00
Wez Furlong db5a6dd1ce tsa: allow http shaping sources to fail
A couple of scenarios we're shooting for here:

* Startup ordering races, or other "burps" due to eg: restarting the
  tsa daemon to update its config
* For "replication" scenarios, you may be running multiple instances
  of tsa-daemon on multiple nodes, and list them all in both the
  publish and subscribe lists on the clients.
  In that situation the client will try to read from each node;
  the data its reads should be essentially the same from each of them,
  and it is fine if only one out of the set responds, or honestly,
  even if none of them respond in the moment, as we'll eventually
  be able to read that data.

Errors making those http requests are logged to the diagnostic log,
but the shaping data is otherwise allowed to load.
2023-08-11 06:22:46 -07:00
Wez Furlong 29ce975970 kcli: add routing_domain support to bounce API and commands 2023-08-10 22:02:07 -07:00
Wez Furlong af59a33650 cidr map: improve error message for bad ip address syntax
We now generate more context-rich error messages to help pinpoint
the problem:

```
caused by: failed to parse '10.0.0.1/24' as CIDR notation: host part of address was not zero
```
2023-08-10 18:05:16 -07:00
Wez Furlong 7d450fdb41 add kumo.cidr module and kumo.cidr.make_map 2023-08-10 17:21:20 -07:00
Wez Furlong 45993ed1f2 shaping: add warning for [cox.net] instead of ["cox.net"]
It's easy to typo this and get surprised by the results, so explicitly
check for it and provide a more actionable message than the DNS
resolution error for `cox.` would imply:

```
Entry for domain 'cox' consists of a single DNS label. Domain names in TOML sections need to be quoted like '["cox.com"]` otherwise the '.' will create a nested table rather than being added to the domain name.
error resolving MX for cox: no record found for Query { name: Name("cox."), query_type: A, query_class: IN }. Ignoring the shaping config for that domain.
```
2023-08-10 09:26:02 -07:00
Wez Furlong 3fd76dd45a introduce routing_domain concept
Augments our queue name format to be
`campaign:tenant@domain!routing_domain`.

The routing_domain is optional.  If the routing_domain is not set, its
effective value is that of the recipient domain.

You can `msg:set_meta('routing_domain', 'bar.com')` to set the
routing_domain for a message, so if the original recipient was
`user@foo.com`, that would cause the computed queue name for it to be
`foo.com!bar.com`.

The routing_domain is used when deciding on the ready_queue name
and destination MXs, so continuing our example, instead of resolving
`foo.com` MX records we'd resolve `bar.com` and deliver to that site.

The `get_egress_path_config` event `domain` parameter is redefined to be
the effective `routing_domain`.

The `get_queue_config` event `domain` parameter is the regular recipient
domain. The `routing_domain` is not currently made available to
`get_queue_config`. If/when we expose it, it will likely be via a
queue name object instead of adding an additional parameter. That would
be a breaking change.

The consequence of not exposing this parameter is that per-message
routing scenarios for the same domain (but different routing domains)
cannot vary the scheduled queue parmeters (eg: retry intervals). Even
though they would have separate scheduled queue instances, those
instances would have the same scheduled queue parameters.  If you need
to be able to do that, then explicitly setting the domain portion of the
queue name would be a way to do that: `msg:set_meta('queue',
'foo.com-via-bar.com!bar.com')`.  `get_queue_config` would then be
called with `domain='foo.com-via-bar.com'` and your policy could then
respond accordingly.
2023-08-10 08:43:20 -07:00
Wez Furlong bcd2946c53 make_queue_config: now supports protocol.smtp.mx_list for smart hosting
Previously, you would do either:
  `msg:set_meta('queue', 'smart.host.domain')`
or
  `msg:set_meta('queue', '[10.0.0.1]')`

to override the effective domain for a message and cause it to be routed
to somewhere other than the recipient domain.

That was OK for basic smart hosting, but limiting when you wanted to use
multiple candidate hosts.

This commit expands the queue config `protocol` field to support
specifying an explicit list of MX hosts that should be used instead.

The integration tests have been migrated away from the old style to this
new style.

While adding plumbing for this, I uncovered an inconsistency between the
queue name generated for the ready queue and the name used by suspension
handling. The inconsistency was introduced in
0842a0fc8b and related work.  This commit
resolves it.
2023-08-09 23:04:45 -07:00
Wez Furlong fbb140026d kcli queue-summary: use stop emoji rather than pause emoji
It looks clearer and easier to understand than the pause emoji,
especially at normal terminal font sizes.
2023-08-07 08:06:39 -07:00
Wez Furlong 927e18dd58 kcli queue-summary: use emoji to indicate suspensions and bounces
Use the pause emoji for suspensions, and the wastebasket emoji for
bounces.  These are shown in the final column of the respective
sections.

Note that for bounces there will only be a short time window where you
will see a bounced domain show up in the list because the bounce will
remove it from the system fairly quickly.
2023-08-05 13:43:58 -07:00
Wez Furlong 78fb8d5450 ready_queue: avoid potential counter race when draining ready queue
There were a few cases where we'd drain the fifo and then explicitly
set the metrics counter to 0.  Critically, the drain and the counter
update were not atomic wrt. other actors that might be inserting
data into the queue, so there was potential to race and perturb
the ready queue count.

This commit avoids every unilaterally setting the new value in
the ready queue module, and instead factors out the drain operation
to collect the messages and then adjust the count by the number
removed.
2023-08-05 11:06:44 -07:00
Wez Furlong 93423f7778 kcli queue-summary: parallelize domain resolution with --domain
Resolve them all concurrently
2023-08-05 10:27:00 -07:00
Wez Furlong 5e966f4571 kcli queue-summary: use natural sort for names
So that `source10` sorts after `source9` rather than `source1`
2023-08-05 09:57:51 -07:00
Wez Furlong 7b63f7d80d kcli queue-summary: add --domain option
Allows filtering the results to just those queues associated
with the requested domain.  Uses site names to map domains
to their associated ready queues.
2023-08-04 22:57:46 -07:00
Wez Furlong 6b8a6b8435 kcli: add queue-summary command
Helpful for an at-a-glance idea of what's happening
2023-08-04 20:19:45 -07:00
Wez Furlong 4373fc8f99 report delivery protocols differently in metrics
Since the refactoring that made ready_queue more abstract,
I noticed that every dispatcher implementation was reported
as `smtp_client`, which is a bit non-sensical for lua and maildir.

This commit tweaks that so that we roll those up as `smtp_client`,
`lua` and `maildir` in the metrics.
2023-08-04 18:22:14 -07:00
Wez Furlong 8e0d7b2c07 kcli: add default for --endpoint, and allow reading from env 2023-08-04 18:19:22 -07:00
Wez Furlong 2890138d90 refine handling of suspend=true
and add docs
2023-08-04 11:05:08 -07:00
Wez Furlong d16b1d0fa5 tsa: add function to configure tsa db path 2023-08-04 11:05:08 -07:00
Wez Furlong f68fb6a08b tsa-daemon: tidy up log levels 2023-08-03 11:40:05 -07:00
Wez Furlong c8a040dfc4 tsa: teach shaping.lua to pull from the tsa-daemon
Refine the egress path update logic as part of that, so that we wake
sleeping Dispatchers to have them notice when it changes.
2023-08-03 11:36:02 -07:00
Wez Furlong a327e3db0c ready_queue: periodically refresh path_config
To facilitate more dynamically updating the configuration, this commit:

* Introduces a ConfigHandle type to aid in building shared configuration
  objects that don't require full mutex interlock
* Switches ReadyQueue and Dispatcher to hold egress path config in a ConfigHandle
* ReadyQueue maintainer will now refresh, by calling
  get_egress_path_config, the value in the config handle
* shaping.lua now uses a ttl of 1 minute (which is the same as the
  ReadyQueue maintainer interval), so that the ready queues should
  reflect egress path configuration changes approximately every minute.
2023-08-03 08:20:40 -07:00
Wez Furlong e132038624 add egress_path_config suspend option, use that for tsa
The simplest way to convey suspension status is via a config override.
So let's allow configuring a path as suspended and deliver that
alongside the other configuration shared by the tsa-daemon.

Some additional work is needed in kumod to ensure that we reload
the path config while the ready queue is in existence; that will
occur in a follow up commit.
2023-08-03 06:04:26 -07:00
Wez Furlong e53ccc491f tsa: add get_config_v1 endpoint
This produces commented shaping.toml output corresponding to any config
overrides set by the automation.

For example, this non-sensical rule that applies to every domain:

```toml
[["default".automation]]
regex = "250 2\\.0\\.0 Ok"
action = {SetConfig={name="max_connection_rate", value="100/s"}}
trigger = {Threshold="2/hr"}
duration = "30 secs"
```

when triggered for messages sent to my own domain:

```console
  $ curl -s 'http://localhost:8008/get_config_v1'
  # Generated by tsa-daemon
  # Number of entries: 1

  ["wezfurlong.org"]

  ["wezfurlong.org".sources]

  ["wezfurlong.org".sources.unspecified]
  mx_rollup = false
  # reason: automation rule: 250 2\.0\.0 Ok
  # expires: 2023-08-03T01:47:37+00:00
  max_connection_rate = "100/s"
```
2023-08-02 18:53:03 -07:00
Wez Furlong d2cdd0ca0a tsa: create schema to recall events and actions
This is using an in-memory sqlite db to keep track of events
and actions that we trigger.

In the future, the db will be persisted at a configurable location
on local storage.

Next step is to add endpoints for both config overrides and suspensions
that can be consumed by the mta nodes.
2023-08-02 16:42:33 -07:00
Wez Furlong 5b2cd26d02 data-loader: fix kumo.secrets.load return type
We were returning a lua table holding the returned bytes
instead of a lua byte string.
2023-08-02 12:30:39 -07:00
Wez Furlong 0842a0fc8b ready_queue: disambiguate queue names by protocol
Include `@protocol-info` on the end of the ready queue names.

This prevents surprising false sharing of custom delivery protocols
when returning different protocols based on the tenant or campaign
metadata.

This is visible in the stats:

```json
  "total_connection_count": {
    "help": "total number of active connections ever made",
    "type": "counter",
    "value": {
      "service": {
        "smtp_client": 1.0,
        "smtp_client:source2->(in1-smtp|in2-smtp).messagingengine.com@smtp": 1.0
      }
    }
  },
```

and perhaps unexpectedly in the `site_name` field of the json log
records. That will need to get tidied up in a separate commit.
2023-08-02 11:12:22 -07:00
Wez Furlong 8db3ab17d6 shaping: expand automation rules with trigger and SetConfig
allows stuff like:

```toml
[["default".automation]]
regex = "250 2\\.0\\.0 boop"
action = {SetConfig={name="max_connection_rate", value="100/s"}}
trigger = {Threshold="2/hr"}
duration = "2 hours"
```

This commit only enables parsing this information; no action is
taken on it at this time.
2023-08-02 10:43:48 -07:00
Wez Furlong e5e8dd11f9 vault: refine integration tests 2023-08-01 16:33:46 -07:00
Wez Furlong f4f1172b56 vault: add to CI so we can run tests 2023-08-01 14:41:28 -07:00
Wez Furlong d33c64b9b0 vault: add integration test and fixup data loading
I think something got screwed up somewhere, because I'm sure this
used to work, but: the value wasn't being parsed out of the loaded
data.

Add an integration test to assert that we can load things.

This test may need some auto-detection to run successfully on CI.
Let's see what happens.
2023-08-01 13:58:50 -07:00
Wez Furlong 16ea223ae8 tsa: basic rule matching
This is plumbing to perform rule matching.
It doesn't do anything useful with matches yet.
2023-08-01 13:58:50 -07:00
Wez Furlong 0565daf790 tsa: load shaping data on startup 2023-08-01 10:12:43 -07:00
Wez Furlong 25dfc798c3 nodeid: allow for missing kumo etc dir when running tests 2023-08-01 08:03:22 -07:00
Wez Furlong 0e39fe35ff domain-map: allow use with memoize
This allows us to avoid rebuilding the map on each message reception
when using the dkim helpers, which should improve the performance
for sites with large numbers of signing domains.
2023-07-31 16:57:16 -07:00
Wez Furlong 45e1564459 shaping: use the rust parser in shaping.lua 2023-07-31 16:57:15 -07:00
Wez Furlong 4869307b89 shaping: allow memoize to work with Shaping struct 2023-07-31 16:57:15 -07:00
Wez Furlong 06e530f843 memoize: allow rust types to be memoized
They need to opt-in to that behavior, but that should be a one-liner.
2023-07-31 16:57:15 -07:00