The SMTP courier was not properly closing the connection, and chasquid's
closing of incoming connections was not ideal (it was closing the
underlying one, not necessarily the active one, like in the case of a jump
to TLS).
This patch fixes both by adding the missing calls to Close.
This patch tidies up the MAIL FROM and RCPT TO handling, in particular:
- Preserve the case on received email. It could be outgoing and we
should not change it.
- Accept (but ignore) RCPT TO options, instead of failing.
- Fix some error codes to make them follow the RFC.
This patch reviews various debug and informational messages, making more
uniform use of tracing, and extends the monitoring http server with
useful information like an index and a queue dump.
This patch adds a package for evaluating SPF, as defined by RFC 7208
(https://tools.ietf.org/html/rfc7208).
It doesn't implement 100% of the RFC, but it coves enough to handle the
most common cases, and will fail open on the others.
If there's an alias to forward email to a non-local domain, using the original
From is problematic, as we may not be an authorized sender for it.
Some MTAs (like Exim) will do it anyway, others (like gmail) will construct a
special address based on the original address.
This patch implements the latter approach, which is safer and allows the
receiver to properly enforce SPF.
We construct a (hopefully) reasonable From based on the local user, and
embedding the original From (but transformed for IDNA, as the receiver may not
support SMTPUTF8).
In the SMTP courier, we should always include a domain when saying hello, as
many MTAs will be pick about it.
An empty domain can happen if the envelope-from is <>. In that case, we fall
back to our hostname.
This patch makes the SMTP courier distinguish permanent errors, so the queue
can decide whether to retry or not.
It's based on the SMTP replies: 5xy is considerent permanent, anything else is
transient (including errors other than protocol issues).
This patch adds SMTPUTF8 to the SMTP courier.
It introduces a new internal/smtp package that extends Go's net/smtp with
SMTPUTF8 (in a very narrow way, not for general use), and then makes the
courier use it.
Also use an IDNA-safe version when saying hello, otherwise servers could
complain if the hostname is not ASCII, and at that stage we don't know if they
support it or not.
This patch adds initial support for SMTPUTF8, which for now consists of just
advertising it.
We support most of it, but sending emails over SMTP requires further work, as
the SMTP courier does not support this yet (it's not in Go's standard
library). That will come in subsequent patches, along with IDNA handling.
https://tools.ietf.org/html/rfc6531.html
This patch implements the first steps of support for IDNA (Internationalized
Domain Names for Applications).
Internally, we maintain the byte-agnostic representation, including
configuration.
We support receiving IDNA mail over SMTP, which we convert to unicode for
internal handling.
Local deliveries are still kept agnostic.
For sending over SMTP, we use IDNA for DNS resolution, but there are some
corner cases pending in the SMTP courier that are tied with SMTPUTF8 and will
be fixed in subsequent patches.
Make the SMTP courier fall back to the A record when MX does not exist, as per
standard behaviour.
This is not implemented nicely, because Go's API does not give a clear signal
if the answer was that there are no MX records or something else happens.
For now, we implement it with a heuristic that should work pretty reliably,
but it's definitely not very nice.
Some servers, like postfix, will pay close attention to the domain we say as a
part of the EHLO.
By default, Go's smtp package will use "localhost", causing it to complain.
This patch fixes that by using the envelope-from's domain.
It's not clear if that's better than using what we are resolving to, but
that's much more involved so we're going to do this for now.
There are cases, like email bounces and forwarding, where a remote server may
use an address within our domain as "MAIL FROM".
The current test at MAIL FROM will block them, which can be quite an
inconvenience as those cases are not that rare.
It's a nice test but doesn't add much, as we don't really pass the validation
along, and we still do relay and user checks on RCPT TO.
So this patch removes that test.
This patch adds a small utility called "smtp-check" that will perform basic
checks on the SMTP setup for the given domain.
Only basic things are implemented for now.
There are a couple of places where it's handy to print TLS constants in
human-readable form.
To do so, we need functions that take TLS constants (usually in uint16 form)
and give us friendly strings.
Go's crypto/tls package does not provide us with this, so this patch
introduces a new module with that purpose.
Having the certificates inside the domain directory may cause some confusion,
as it's possible they're not for the same name (they should be for the MX we
serve as, not the domain itself).
So it's not a problem if we have domains with no certificates (we could be
their MX with another name), and we could have more than one certificate per
"domain" (if we act as MXs with different names).
So this patch moves the certificates out of the domains into a new certs/
directory, where we do a one-level deep lookup for the files.
While at it, change the names of the files to "fullchain.pem" and
"privkey.pem", which match the names generated by the letsencrypt client, to
make it easier to set up. There's no general convention for these names
anyway.
This patch performs some minor cleanups for things detected by "go vet":
- Remove one line of unreachable code.
- Don't leak contexts until their deadline expires, cancel them.
It's more convenient and in line with standard practice to fail RCPT TO if the
user does not exist.
This involves making the server and client aware of aliases, but it doesn't
end up being very convoluted, and simplifies other code.
This patch adds a print-config option that will parse a config and print it
(in text protobuf format).
It can be used to validate configurations, and see what the effective
configuration is (that is, including the defaults).
It's often useful to run the tests with the race detector (-race) enabled.
Unfortunately, building with it is too slow to enable unconditionally.
So for now this patch adds an option, in the form of an environment variable,
to enable it manually.
Today, if the aliases file does not exist when chasquid starts up, the entire
domain will be skipped from aliases resolution.
That's a bug, as it means we don't perform character and suffix replacements
for known domains, and is also an inconvenience as it forces us to reload the
daemon when adding a file for a known domain.
This patch fixes this by adding them unconditionally, even if the file does
not exist.
This patch adds a test for delivery status notifications and null address
deliveries, that check that chasquid can both receive and send DSNs.
To do this, we extend the mail_diff utility to support wildcards in the
comparisons, to skip over variable parts of the messages (like dates).
When we permanently failed to deliver to one or more recipients, send delivery
status notifications back to the sender.
To do this, we need to extend a couple of internal structures, to keep track
of the original destinations (so we can include them in the message, for
reference), and the hostname we're identifying ourselves as (this is arguable
but we're going with it for now, may change later).
We can send and received with a <> "mail from", which is explicitly allowed.
Internally, we use "<>" to represent it. This requires special-casing in a
couple of places, but makes sure the handling is explicit, and we don't
accidentally confuse it with not having a source address.
This patch fixes some inconsistencies with this handling.
This patch makes the queue and couriers distinguish between permanent and
transient errors when delivering mail to individual recipients.
Pipe delivery errors are always permanent.
Procmail delivery errors are almost always permanent, except if the command
exited with code 75, which is an indication of transient.
SMTP delivery errors are almost always transient, except if the DNS resolution
for the domain failed.
This patch removes chasquid-userdb and adds a more generic and extensive
chasquid-util, that supports various operations on user databases as well as
aliases lookups.
The code is not very pretty but for now I took a more practical approach, the
tool is ancillary and can be tidied up later.
This patch tidies up the Procmail courier:
- Move the configuration options to the courier instance, instead of using
global variables.
- Implement more useful string replacement options.
- Use exec.CommandContext for running the command with a timeout.
As a consequence of the first item, the queue now takes the couriers via its
constructor.
With the introduction of aliases, the queue may now be delivering mail to
pipes. This patch implements pipe delivery.
It uses a fixed 30s timeout for now, as these commands should really not take
much time, and we don't want to overly complicate the configuration for now.
aliases databases can be very useful, so this patch adds a package to parse
and resolve aliases.
It uses an existing, well known and widely used format for aliases, although
it doesn't necessarily match 100% any existing implementation at the moment.
This patch makes the queue read and write items to disk.
It uses protobuf for serialization. We serialize to text format to make
manual troubleshooting easier, as the performance difference is not very
relevant for us.
This patch adds a "data_dir" option, that chasquid will use to store
persistent data. It defaults to "/var/lib/chasquid" (for now, at least).
Users will come in subsequent patches.
In some cases, it's be useful to have references to directories relative
to the configuration itself.
So this patch makes chasquid do a Chdir into it, so we can assume it in
the rest of the code and config.
To avoid user and automation confusion, prefix the temporary files with
a ".". That way, if something scans the directory for files, it's less
likely to encounter one of our temporary files.
This will become very relevant in subsequent patches.
This is just a run of "go generate ./..." which re-generates the protocol
buffers code.
There is no actual change in the logic or APIs, it's just a rebuild with a
newer proto compiler. While it's not strictly necessary, it will help make
subsequent patches cleaner.
This patch adds checks that verify:
- The envelope from must match the authenticated user. This prevents
impersonation at the envelope level (while still allowing bounces, of
course).
- If the destination is remote, then the user must have completed
authentication. This prevents unauthorized relaying.
The patch ends up adjusting quite a few tests, as they were not written
considering these restrictions so they have to be changed accordingly.
We want to be able to distinguish between connections for SMTP and connections
for submission, so we can make different policy decisions.
To do that, we first make the configuration aware of the different kinds of
addresses. This is done in this patch in a backwards-incompatible way, but at
this point in time it is ok to do so.
Then, we extend systemd's socket passing library to support socket naming, so
we can tell the different sockets apart. This is done via the
LISTEN_FDNAMES/FileDescriptorName mechanism.
And finally we make the server and connection types aware of the socket mode.