staticcheck found a couple of minor code cleanup improvements, like
unused variables or an out-of-order defer, mostly in tests.
This patch fixes those problems by making the necessary adjustments.
They're all fairly small, and should not change the logic in any
significant way.
There are a couple of places in the tests when we attempt to build and
run simultaneously. Here, if the build is slow, there is a race where
"text file busy" can appear.
To fix this, build to a temporary file with a random name, then
atomically rename it to the final binary name.
This patch implements "via" aliases, which let us explicitly select a
server to use for delivery.
This feature is useful in different scenarios, such as a secondary MX
server that forwards all incoming email to a primary.
For now, it is experimental and the syntax and semantics are subject to
change.
We have a few Python scripts which over the years ended up with a
variety of formatting.
This patch auto-formats them using `black -l 79` to make them more
uniform, and easier to read and write.
minidns supports MX records, but today it hard-codes priority=10.
This is limiting when creating test scenarios that depend on having
different MX priorities.
This patch adds support for specifying the priority in MX records.
ssl.wrap_socket() has been deprecated and is no longer functional in
Python 3.12: https://docs.python.org/3/whatsnew/3.12.html#ssl.
This patch replaces it with the equivalent (in this context)
ssl.SSLContext.
This patch adds a cross-tool integration check that uses
driusan/dkim's dkimverify to confirm it can verify our own DKIM signatures.
It is optional, since the tool may not be present.
To send mails, today some tests use msmtp and others our internal smtpc.py.
This works, but msmtp slows down the tests significantly, and smtpc.py
is also not particularly fast, and also has some limitations.
This patch introduces a new SMTP client tool written in Go, and makes
almost all the tests use it.
Some tests still remain on msmtp, mainly for client-check compatibility.
It's likely that this will be moved in later patches to a separate
special-purpose test.
With this patch, integration tests take ~20% less time than before.
Our tests invoke a variety of helpers, some of them are written in Go.
Today, we call "go build" (directly or indirectly via "go run"), which is
a bit wasteful and slows down the tests.
This patch makes the tests only build our Go helpers once every 10s at
most.
The solution is a bit hacky but in the context of these tests, it's
practical.
The generate_cert cache has a bug because it uses the directory's age,
which won't necessarily change, and it was always re-generating
certificates after 10m.
This patch fixes the bug by checking the age of the private key file
instead of the directory.
The RFCs are very clear that in DATA contents:
> CR and LF MUST only occur together as CRLF; they MUST NOT appear
> independently in the body.
https://www.rfc-editor.org/rfc/rfc5322#section-2.3https://www.rfc-editor.org/rfc/rfc5321#section-2.3.8
Allowing "independent" CR and LF can cause a number of problems.
In particular, there is a new "SMTP smuggling attack" published recently
that involves the server incorrectly parsing the end of DATA marker
`\r\n.\r\n`, which an attacker can exploit to impersonate a server when
email is transmitted server-to-server.
https://www.postfix.org/smtp-smuggling.htmlhttps://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/
Currently, chasquid is vulnerable to this attack, because Go's standard
libraries net/textproto and net/mail do not enforce CRLF strictly.
This patch fixes the problem by introducing a new "dot reader" function
that strictly enforces CRLF when reading dot-terminated data, used in
the DATA input processing.
When an invalid newline terminator is found, the connection is aborted
immediately because we cannot safely recover from that state.
We still keep the internal representation as LF-terminated for
convenience and simplicity.
However, the MDA courier is changed to pass CRLF-terminated lines, since
that is an external program which could be strict when receiving email
messages.
See https://github.com/albertito/chasquid/issues/47 for more details and
discussion.
This patch makes mail_diff more strict in its parsing, to ensure we
catch any encoding issues that may otherwise be masked by the default
compatibility policy.
This patch makes chasquid-util's aliases-resolve and domaininfo-remove
commands talk to the chasquid server (via the new localrpc server).
For aliases-resolve, currently has fairly hacky logic which reimplements
a bunch of the servers', and is also incomplete because it does not
support hooks.
In this patch we fix that by having it talk to the server, where we get
authoritative responses and have no issues with aliases hooks. This
resolves https://github.com/albertito/chasquid/issues/18.
For domaininfo-remove, currently its implementation is also very hacky
since it manipulates files behind the servers' back and without even
using the internal library.
In this patch we fix that by doing the operation through the server,
avoiding the need for those hacks, and also remove the need to manually
reload the server afterwards.
Go 1.20 finally includes proper support for instrumenting binaries for
coverage. This allows us to drop quite a few hacks and workarounds that
we used for it, and we can now also test exiting cases.
The downside is that coverage tests now require Go 1.20, but it is an
acceptable price to pay for the more accurate results.
Normal integration tests are unchanged.
This patch updates the coverage testing infrastructure to make use of
the new Go 1.20 features.
This patch updates the shell scripts with some of the common best
practices, which should make them more resilient to unusual failures and
unexpected environments (in particular, directories with spaces).
Most of these were identified by shellcheck.
The integration tests spend a lot of time on some ancilliary actions,
which slows them down: generating certificates, adding users, and
waiting for things to happen.
To improve the performance of those actions, this patch:
- Makes (most) tests use plain passwords (-20%)
- Adds a certificate cache to reuse certs (-10%)
- Tightens the sleep loops (-5%)
In aggregate, this patch results in a speedup of the integration tests
of ~30-40%.
Note that some of the tests required adjusting the username, because
`chasquid-util user-add` would convert them to lowercase as per PRECIS
mapping.
This patch changes several internal packages to receive and pass tracing
annotations, making use of the new tracing library, so we can have
better debugging information.
ioutil package was deprecated in Go 1.16, replace all uses with their
respective replacements.
This patch was generated with a combination of `gofmt -r`, `eg`, and
manually (for `ioutil.ReadDir`).
This patch is the result of running Go 1.19's `gofmt` on the codebase,
which automatically updates all Go doc comments to the new format.
https://tip.golang.org/doc/go1.19#go-doc
We've accumulated a few linter issues around comments and a couple of
variable names.
While none of them is major, this patch cleans them up so it's easier to
go through the linter output, and we can start being more strict about
it.
The current generate_cert helper was originally taken from Go's source,
and is more complex than we need it to be.
This patch replaces it with our own version, rewritten from scratch
independently.
This patch moves the test helper binaries to a "one directory per
helper" layout, and also makes them to be ignored in the coverage build
instead of all builds.
With this change, "go build ./..." will build all binaries including the
test helpers, which helps make sure that module manage automation also
considers them. In particular, this makes "go mod tidy" work fine.
The `which` command isn't guaranteed to be available, it is just
extremely common; `command -v` is the standard way to do find an
executable program. See https://lwn.net/Articles/874049/ for more
details.
This patch replaces the uses of `which` with `command -v`, which only
appears in a couple of tests.
When resolving MX records, we need to distinguish between "no such
domain" and other kinds of errors. Before Go 1.13, this was not
possible, so we had a workaround that assumed any permanent error was a
"no such domain", which is not great, but functional.
Now that our minimum supported version is Go 1.15, we can remove the
workaround.
This patch replaces the workaround with proper logic using
DNSError.IsNotFound to identify NXDOMAIN results when resolving MX
records.
This requires to adjust a few tests, that used to work on environments
where resolving unknown domains (used for testing) returned a permanent
error, and now they no longer do so. Instead of relying on this
environmental property, we make the affected tests use our own DNS
server, which should make them more hermetic and reproducible.
Most integration tests depend on the $HOSTALIASES environment variable
being functional. That variable works on most systems, but not all. In
particular, systems with `systemd-resolved` can cause the variable to be
ignored.
This was reported by Alex Ellwein in
https://github.com/albertito/chasquid/issues/20.
This patch makes the affected tests to be skipped if $HOSTALIASES is not
working properly. It also removes unnecessary hosts files from tests
which don't need it, and documents this behaviour.
Thanks to Alex Ellwein and foxcpp@ for reporting and helping investigate
this issue!
The docopt-go library is quite convenient, but it has been abandoned for
a while :(
Since we only use it for chasquid-util, this patch removes it and
replaces it with a custom small parser, that is a reasonable fit for the
required use cases.
The patch also adds a couple of tests to increase coverage.
NOTE: docopt-go accepted some undocumented behaviour, in particular the
use of "-a b" instead of "-a=b". The new parser does not, so some
user scripts may require updating.
I think this should be rare enough not to be worth the complexity of
adjusting the parser to allow it.
fexp is a testing utility, including it in the regular Go build confuses
some automation as it can think it's part of chasquid proper.
All other testing utilities are ignored via the "+build ignore"
annotation for this reason, so this patch adds it to fexp to fix this
issue.
This patch implements support for incoming connections wrapped in the
HAProxy protocol v1.
This is useful when running chasquid behind a HAProxy server, as it
needs the original source IP to perform SPF checks.
This patch is a reimplementation of one originally provided by Denys
Vitali in pull request #15, except the logic for the protocol handling
is moved to a new package, and the smtpsrv.Conn handling of the source
IP is simplified.
It is marked as experimental for now, since we want to give it a bit
more exposure just in case the option/api needs adjustment.
Thanks a lot to Denys Vitali (@denysvitali in github) for sending the
original patch for this, and helping test it!
Allows terminating chasquid via the network. Useful to trigger a restart
(if there is an init system to relaunch chasquid) and thus reload certificates.
Amended-by: Alberto Bertogli <albertito@blitiri.com.ar>
Added tests, and adjusted shutdown sequence.
This patch removes the dependency on wget for fetching content over
http, which was used in one of the tests to do some checking on debug
and metric pages, as well as loop detection.
Instead of wget, we now use a small built-in utility called fexp.
If the load generator is sending emails too fast, chasquid queue might
hit the maximum size and fail the test.
This patch makes it sleep and retry, to give the server some time to
catch up.
Thanks to Max Mazurov (fox.cpp@disroot.org) for reporting this problem.
This patch adds a new integration test to cover SPF checks. The main
goal is not to cover the SPF parsing, since that's handled by the
library already, but the higher level aspects: that the mails are indeed
rejected, that the DSN looks reasonable, etc.
To make the coverage report a bit more accessible and easier to
navigate, this patch makes the coverage tests generate a new HTML
coverage report (in addition to the classic variant).
When modules are configured, go will now attempt to fetch them on build,
using the proxy.
That causes the integration tests run via Docker to fail, because go
build attempts to contact the proxy within the test environment, which
has the external DNS resolvers disabled.
This patch fixes the problem by adjusting the pre-run fetch to use the
new modules system. We fetch the dependencies to the cache so that when
they're needed within the environment, they are present and there's no
need to use the proxy.
In some distributions, including newer Debian versions, msmtp is
installed as setgid.
That prevents $HOSTALIASES from being honoured, which breaks the tests.
This patch works around the problem by creating a copy of the binary,
which will not have the setgid bit set.
Python 2 is approaching end of life, and we only need it to run
the mail_diff test utility.
This patch updates mail_diff to run on Python 3, which only needed minor
changes.
The tests might start running and attempting to do DNS resolutions
before minidns has come up, which can cause false positives and flaky
tests.
This patch makes the entrypoint wait until minidns has come up, to fix
the problem.
The integration tests depend on having a DNS server that resolves
"localhost", which is unfortunate but currently unavoidable given
glibc's limitations ($HOSTALIASES only works on DNS-level aliases, and
does not do lookups in /etc/hosts).
Even under docker, this makes the tests depend on the DNS server, and
whether it resolves localhost or not.
In order to make the docker tests more hermetic and isolated from the
environment, this patch introduces a docker entrypoint that, within the
container, will launch minidns and override /etc/resolv.conf to use it.
This guarantees that the tests will be able to resolve localhost, and
also avoid accidental reliance on external DNS zones.
This is a mini-DNS server for testing purposes.
This can be used to set up hermetic tests in containers, and work around
glibc's limitation of being unable to create per-process host aliases.
This patch adds DKIM signing using https://github.com/driusan/dkim tools
to the example hook.
It also adds an optional integration test to exercise signing and
verification, and corresponding documentation.
This patch adds more tests for the dovecot library, in particular:
- Protocol errors (invalid versions, etc.).
- Invalid command (cli-specific test).
- Connection breakups.
This patch adds a new test, which verifies the TLS tracking.
Because we need to simulate SPF records, and Go does not support fully
intercepting DNS lookups yet, this test relies on dnsmasq to provide a
DNS resolver.
In the future, once Go supports DNS lookup interception, we can get rid
of this additional dependency.
SIGTERM can happen normally in our tests, and the current code has a
trap that makes it trigger a clean exit.
This causes some errors to be masked, as we end up calling "exit 0" when
they occur. The error message will still be displayed, but the caller
script will assume it worked.
This patch fixes the problem by adjusting the bash signal handlers, so
that we ignore SIGTERM (so bash does not get killed by the exit handler)
and exit with error on SIGINT (triggered by ctrl-c).
Note that under some conditions the SIGTERM trap is not necessary, but
this depends on the environment.
Our chamuyero tests involve reading and writing utf8. This usually
works, but is dependent on the environment: on LC_ALL=POSIX environment,
for example, Python enforces ascii as the default encoding, and the
tests break.
So this patch makes chamuyero explicitly set utf8 encodings in stdout
and all the sockets.
This patch introduces stress tests, which put load on the daemon to help
sanity check its behaviour under stress.
They are separate from the existing integration tests, which focus on
correctness.
Two tests are included here: a load test, which sends emails repeatedly;
and a connection test which opens as many conections as possible.