Commit Graph
65 Commits
Author SHA1 Message Date
Wez Furlong 06bb8d0492 add msg:add_authentication_results
This method will prepend an Authentication-Results header
to the message with the specified set of results and
local server name.

We need a more convenient way to default the server name;
ideally we'd pick this up from the listener, but that is currently
awkward in the policy.

refs: https://github.com/KumoCorp/kumomta/issues/82
2023-09-14 13:03:41 -07:00
Wez Furlong e4bb5d674d add msg:dkim_verify()
This method returns an array of AuthenticationResult reflecting
the verification status.

refs: https://github.com/KumoCorp/kumomta/issues/82
2023-09-14 13:03:41 -07:00
Wez Furlong 134014d988 message: avoid rebuilding when the fix only requires adding headers 2023-08-29 17:22:09 -07:00
Wez Furlong 0b21193b8a add msg:check_fix_conformance()
This is for check and/or fixing message conformance issues.
2023-08-29 16:18:59 -07:00
Wez Furlong a36eee80f0 message: make retain_headers more efficient
Avoid an intermediate string allocation; we can write directly
to the new buffer.
2023-08-25 08:55:30 -07:00
Wez Furlong ff9937d8db message: replace mailparse dep with our mailparsing crate
The tests that are fixed up as part of this show that we're doing a
better job at preserving technically bogus stuff like `Name :value`.
2023-08-25 08:55:29 -07:00
Wez Furlong ce8596f248 message: mostly adopt mailparsing
This doesn't replace the retain/remove methods,
because we don't have that aspect of the API fleshed out
yet.
2023-08-25 08:55:29 -07:00
Wez Furlong e03ee5badf message: replace mailparser dep with mailparsing
Use our own parser
2023-08-25 08:55:28 -07:00
Wez Furlong 909cc95001 dkim: improve error reporting around message parsing
Use Result rather than Option.
Bubble up non-canonical line endings explicitly as an issue.
2023-08-25 08:55:28 -07:00
Wez Furlong 452bede2e1 dkim: switch to our own mailparsing implementation 2023-08-25 08:55:28 -07:00
Wez Furlong 6fead2d6b2 rename our forked dkim crate
We're diverging a decent amount, so let's use our own name
2023-08-25 08:55:28 -07:00
Wez Furlong 05e52d4ce6 message: switch ed25519 dkim signing to cfdkim
With the upgraded ed25519-dalek crate, it's now possible to
pass in either DER or PEM encoded PKCS8 signing keys, which
makes it feasible to remove the mail-auth dep from this crate.

That in turns reduces the amount of code in here, which is nice.
2023-08-22 17:27:06 -07:00
Wez Furlong 71924490d3 message: add set_recipient and set_sender methods 2023-08-15 14:51:35 -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 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 61b84099f0 deps: update bitflags 2023-07-20 13:18:33 -07:00
Wez Furlong dde4b29fba api/cli: add inspect-message API for looking at a message by id
More detailed docs will follow once some other similar APIs are
completed.
2023-07-18 11:22:57 -07:00
M.L. Oelering c7be51ce9e Pluralize remove_all_named_header 2023-07-06 14:29:59 -07:00
M.L. Oelering 72f61662f5 Run make fmt to fix formatting issue 2023-07-06 14:29:59 -07:00
M.L. Oelering 31065230c2 add method remove_all_named_header in lua 2023-07-06 14:29:59 -07:00
Wez Furlong eb6232f582 Add basic suspension of scheduled and ready queues
These are two different groups of queues, so there are two different
sets of things to manage them.

kcli now has `suspend(-list|cancel)?` and
`suspend-ready-q(-list|cancel)?` subcommands for establishing a
suspension, listing the suspensions and cancelling a suspension
in the scheduled-q and ready-q namespaces respectively.

The names of the ready queues can be derived from the metrics API:

```console
$ curl -s 'http://localhost:8000/metrics.json'  | jq .
...
  "ready_count": {
    "help": "number of messages in the ready queue",
    "type": "gauge",
    "value": {
      "service": {
        "smtp_client:source2->(in1-smtp|in2-smtp).messagingengine.com": 0.0
      }
    }
  },
...
```

From the above, `source2->(in1-smtp|in2-smtp).messagingengine.com` is
the name of the underlying ready queue.

We can and probably should add something to `kcli` to make that slightly
easier to review and manage for the operator.

refs: https://github.com/KumoCorp/kumomta/issues/51
2023-06-23 07:50:56 -07:00
Wez Furlong 1085e4bb19 dkim: move rsa key loading into DkimPrivateKey type
This makes it easy for the message crate to use the faster openssl
functionality if that has been enabled for the dkim crate.
2023-06-16 07:49:15 -07:00
Wez Furlong 2e40c00121 dkim: introduce ParsedEmail type
This help transitions to a cheaper email parser, that doesn't need to
parse the entire MIME structure

Note that as part of this, I found that several tests were running
against email messages using unix line endings instead of canonical
CRLF.

This was silently being masked and an empty or otherwise incorrect byte
slice was being hashed; I've updated the affected tests and their test
expectations accordingly, as well as made this error bubble up at the
time of parsing the email, rather than silently squashing it in the
depths of the hasher.
2023-06-15 11:27:03 -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 bcfba07dad dkim: switch to my fork of cfdkim for RSA signatures
That fork removes the lifetime from the Signer type which makes
it easier to cache more efficiently.
2023-06-09 06:46:05 -07:00
Wez Furlong 88fd825be4 dkim: add cfdkim as a runtime alternative RSA signer
The mail-auth crate doesn't support 1024 bit RSA keys, which are
currently the dominant size of key used by senders at scale.

It is not possible to even configure a 2048 bit RSA key in some DNS
providers, which blocks widespread adoption of larger key sizes.

This commit pulls in the cfdkim crate as an alternative implementation.

I'm not happy with this implementation because the API of the crate
defeats some caching optimizations, doesn't support certain signing
attributes that are supported by mail-auth and the generated header isn't
nicely wrapped.

We'll likely "do something" to resolve this in a future commit.

For now, this implementation is enabled by setting `use_cf = true`
in the signing parameters.
2023-06-08 14:29:19 -07:00
Wez Furlong f11511031a dkim: refine error message
I couldn't find definitive evidence that rsa-1024 has been broken,
but I did find some cases where eg: GPG had a side-channel attack
that could break PGP keys of that size, so it's easy to imagine
that other similar approaches might work for other software.

Regardless, the actual limitation comes from the Rust ring crate:

https://docs.rs/ring/latest/ring/signature/struct.RsaKeyPair.html#method.from_pkcs8

> Only two-prime (not multi-prime) keys are supported. The public modulus
> (n) must be at least 2047 bits. The public modulus must be no larger
> than 4096 bits. It is recommended that the public modulus be exactly
> 2048 or 3072 bits. The public exponent must be at least 65537.

Refine the error message to be more factual.
2023-06-08 10:32:42 -07:00
Wez Furlong ac913fad29 dkim: improve error messaging when loading a key fails 2023-06-08 10:13:58 -07:00
Wez Furlong f0e0ffe828 add message:set_force_sync
refs: https://github.com/KumoCorp/kumomta/issues/6
2023-05-08 17:37:54 -07:00
Wez Furlong d6203f6eff Allow logging via a lua hook
Still need to write up some proper docs, but the gist is that
the example below will enqueue a message holding the content
of the JsonRecord for each log event, and that message will be
dispatched via a custom lua delivery mechanism.

```lua
kumo.on('init', function()
  kumo.configure_log_hook {}
end)

kumo.on('make.lua-sender', function(domain, tenant, campaign)
  print 'making lua sender'
  local sender = {}
  function sender:send(message)
    print('Sending a message!', message:get_data())
    return 'Super!'
  end
  return sender
end)

kumo.on('should_enqueue_log_record', function(msg)
  local log_record = msg:get_meta 'log_record'
  print('should_enqueue_log_record', log_record)
  -- avoid an infinite loop caused by logging that we logged
  if log_record.queue ~= 'lua-stuff' then
    msg:set_meta('queue', 'lua-stuff')
    return true
  end
  return false
end)

kumo.on('get_queue_config', function(domain, tenant, campaign)
  print('get_queue_config', domain, tenant, campaign)
  if domain == 'lua-stuff' then
    return kumo.make_queue_config {
      protocol = {
        custom_lua = {
          constructor = 'make.lua-sender',
        },
      },
    }
  end

  return kumo.make_queue_config {}
end)
```
2023-05-04 16:20:29 -07:00
Wez Furlong f9c6eb8ce3 add ed25519 dkim signer 2023-03-16 10:23:54 -07:00
Wez Furlong b3f3f877df add functions for working with address headers 2023-03-14 18:53:47 -07:00
Wez Furlong ec920535e3 refactor: move arf and bounce report parsing to kumo-log-types
This reduces transitive deps for other crates that want ostensibly just
the type definitions.
2023-03-12 20:16:05 -07:00
Wez Furlong 722876a144 first pass at memory awareness
This commit introduces awareness of memory limits that may
have been established for the process.

The idea is that the hard/soft limits are read from the active
cgroup, falling back to classic ulimit hard/soft limits, falling
back to the system RAM size.

In the absence of an explicitly configured hard limit, the RAM size
is used for the hard limit.

In the absence of an explicitly configured soft limit, 75% of the
hard limit is used for the soft limit.

A background thread monitors the memory usage and (potentially
adjusted) memory limits.

A low memory state is when the usage is within 10% of the soft limit.
In this state, we start to trim back memory usage, shrinking loaded
data when messages are placed into the ready queue.

A more severe memory state is when the usage exceeds the soft limit.
In this state, new message reception is rejected until the usage
recovers.

kumod doesn't do anything directly with the hard limit, but assumes
that the system will terminate the process with no opportunity to
safely clean up if that value is reached.

In order to reduce the memory usage, the allocator has been switched
to jemalloc which has superior management of fragmentation and
a control interface to request release of various memory caches.

We make use of jemalloc specific functions in the tokio thread pools;
when they park (idle), we release the thread local cache, and when
we reach the soft limit we aggressively flush the caches until
memory usage falls below the limit.

There's like some room for tuning of the thresholds around this,
but this is a reasonable start.

Note that there are no explicit config options in kumod to set
the soft and hard limits: those are taken from the process
environment and can be simply set either via ulimit in a calling
shell script, or via systemd service configuration.

We may introduce some knobs for the low memory state threshold
in the future.
2023-03-09 18:50:24 -07:00
Wez Furlong 56135506f2 refactor: use any_err in more places 2023-03-05 10:44:54 -07:00
Wez Furlong c184214253 allow loading TLS key+cert from hashicorp vault
refactor to allow reusing the same key loader logic for dkim, smtp
and http.
2023-03-04 11:01:35 -07:00
Wez Furlong 38e60f9274 rename vault object fields to have vault_ prefix
This disambiguates for future alternative credential storage
systems.
2023-03-04 10:29:25 -07:00
Wez Furlong 6204642270 add trace headers configuration and link to ARF parsing 2023-03-04 07:22:02 -07:00
Wez Furlong bfe3c49d77 add rfc5965 ARF report parsing
Refactor the rfc3464 parsing to simplify both of them
2023-03-03 13:58:12 -07:00
Wez Furlong 2000be18d4 fix formatting 2023-03-03 11:14:03 -07:00
Wez Furlong 7d76b3d9d7 add support for retrieving signing keys from hashicorp vault 2023-03-03 10:32:16 -07:00
Wez Furlong ef7fa59355 synthesize OOB log records from OOB reports
* When logging the reception of a message, if we can parse it
  as an rfc3464 oob report, then synthesize OOB records for
  each recipient
* Add config to allow relaying for domains for which bounces
  are received, so that we can accept their message content.
* If the domain config allows bounces but not relaying, then
  the message will not be spooled or queued after reception;
  it will be dropped with no additional logging.
2023-03-03 06:53:33 -07:00
Wez Furlong da5f2433c0 add rfc3464 delivery status report parser 2023-03-02 14:46:30 -07:00
Wez Furlong c7a8405ddf fix code formatting 2023-03-01 09:45:22 -07:00
Wez Furlong 2b0ba702cb add scheduling constraints
This is implemented by allowing the Message:set_due method to
load the message metadata to consult the scheduling constraints,
which should mean that all scheduling updates have the constraints
applied to them.
2023-03-01 09:43:48 -07:00
Wez Furlong 39e08d602b add scheduling restriction datatype
This will be used in the future to adjust the message
sending schedule on a per-message basis.
2023-03-01 09:43:48 -07:00
Wez Furlong 0eefb02bed make destination site queues per-tenant
To facilitate this, formatlize the `campaign:tenant@domain` naming
and add a helper type to parse and format that tuple of information
to make it easier to work with in the code.

Adjust the get_x_config events to accept those parameters.
2023-02-26 19:59:56 -07:00
Wez Furlong 6dfb44012e add some header manipulation functions 2023-02-26 15:01:05 -07:00
Wez Furlong 1d9f65d07b dkim: refactor
This will support adding ed25519 signing in the future,
but stops short because I need to read up on the storage:
the library only supports PKCS8 DER encoded binary data.
2023-02-26 07:27:18 -07:00
Wez Furlong b25ff76118 add dkim signing
A fairly sprawling commit:

* Move config to its own crate to facilitate making things more modular
* Crates/modules can now provide a registration function that can be
  used to setup functions in the lua environment
* Message crate now has a dkim module with a signer type that can
  be loaded from lua; signers are cached (with ttl) and shareable
* Message now has a dkim_sign method that does the signing, as well
  as methods for appending and prepending headers that are necessary
  to support signing.
2023-02-21 21:18:49 -07:00