aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--COPYING8
-rw-r--r--Changelog197
-rw-r--r--Makefile120
-rw-r--r--README93
-rw-r--r--benchmark/dovecot.conf29
-rwxr-xr-xbenchmark/random_maildir.pl2
-rwxr-xr-xbenchmark/run44
-rw-r--r--doc/benchmark.md4
-rw-r--r--doc/build.md73
-rw-r--r--doc/development.md87
-rw-r--r--doc/getting-started.md29
-rw-r--r--doc/index.md16
-rw-r--r--doc/interimap.1.md109
-rw-r--r--doc/multi-account.md25
-rw-r--r--doc/pullimap.1.md112
-rw-r--r--doc/template.html14
-rwxr-xr-xinterimap22
-rw-r--r--interimap.sample8
-rw-r--r--interimap.service3
-rw-r--r--interimap@.service3
-rw-r--r--lib/Net/IMAP/InterIMAP.pm310
-rwxr-xr-xpullimap18
-rw-r--r--pullimap.sample4
-rw-r--r--pullimap@.service3
-rw-r--r--tests/auth-login/t2
-rw-r--r--tests/auth-logindisabled/remote.conf4
-rw-r--r--tests/auth-logindisabled/t2
-rw-r--r--tests/auth-noplaintext/t2
-rw-r--r--tests/auth-sasl-plain-no-ir/t2
-rw-r--r--tests/auth-sasl-plain/t2
-rw-r--r--tests/certs/.gitignore4
-rwxr-xr-xtests/certs/generate57
-rw-r--r--tests/certs/openssl.cnf4
-rw-r--r--tests/compress/t2
-rw-r--r--tests/condstore/t6
-rw-r--r--tests/config/dovecot/dhparams.pem (renamed from tests/snippets/dovecot/dhparams.pem)0
-rw-r--r--tests/config/dovecot/imapd.conf (renamed from tests/snippets/dovecot/imapd.conf)7
-rw-r--r--tests/config/dovecot/interimap-required-capabilities.conf (renamed from tests/snippets/dovecot/interimap-required-capabilities.conf)0
-rw-r--r--tests/config/dovecot/lmtpd.conf (renamed from tests/snippets/dovecot/lmtpd.conf)4
-rw-r--r--tests/config/dovecot/ssl.conf6
-rw-r--r--tests/db-exclusive-lock/t6
-rw-r--r--tests/db-migration-0-1-foreign-key-violation/t2
-rw-r--r--tests/db-no-create--watch/t4
-rw-r--r--tests/db-upgrade-0-1-delim-mismatch/t2
-rw-r--r--tests/db-upgrade-0-1/t6
-rw-r--r--tests/delete/t16
-rw-r--r--tests/delimiter-change/t6
-rw-r--r--tests/ignore-mailbox/t10
-rw-r--r--tests/interimap.list (renamed from tests/list)8
-rw-r--r--tests/largeint/t38
-rw-r--r--tests/list-mailbox/t4
-rw-r--r--tests/list-reference/t8
-rw-r--r--tests/list-select-opts/t18
-rwxr-xr-xtests/preauth-plaintext/imapd44
l---------tests/preauth-plaintext/interimap.remote1
-rw-r--r--tests/preauth-plaintext/t19
-rw-r--r--tests/pullimap.list2
-rw-r--r--tests/pullimap/t78
-rw-r--r--tests/rename-exists-db/t10
-rw-r--r--tests/rename-exists-local/t8
-rw-r--r--tests/rename-exists-remote/t8
-rw-r--r--tests/rename-inferiors/t6
-rw-r--r--tests/rename-simple/t4
-rw-r--r--tests/repair/t28
-rw-r--r--tests/resume/t26
-rwxr-xr-xtests/run65
-rwxr-xr-xtests/run-all5
-rw-r--r--tests/snippets/dovecot/dovecot.key5
-rw-r--r--tests/snippets/dovecot/dovecot.pem11
-rw-r--r--tests/snippets/dovecot/ssl.conf4
-rw-r--r--tests/split-set/t10
-rwxr-xr-xtests/starttls-injection/imapd77
l---------tests/starttls-injection/interimap.remote1
-rw-r--r--tests/starttls-injection/remote.conf8
-rw-r--r--tests/starttls-injection/t16
-rw-r--r--tests/starttls-logindisabled/remote.conf4
-rw-r--r--tests/starttls-logindisabled/t2
-rw-r--r--tests/starttls/t11
-rw-r--r--tests/sync-live-multi/local.conf40
-rw-r--r--tests/sync-live-multi/t24
-rw-r--r--tests/sync-live/t12
-rw-r--r--tests/sync-mailbox-list/t22
l---------tests/tls-ciphers/interimap.remote1
l---------tests/tls-ciphers/remote.conf1
-rw-r--r--tests/tls-ciphers/t31
-rw-r--r--tests/tls-pin-fingerprint/t53
-rw-r--r--tests/tls-protocols/openssl.cnf14
-rw-r--r--[l---------]tests/tls-protocols/remote.conf5
-rw-r--r--tests/tls-protocols/t78
l---------tests/tls-rsa+ecdsa/interimap.remote1
-rw-r--r--tests/tls-rsa+ecdsa/remote.conf5
-rw-r--r--tests/tls-rsa+ecdsa/t59
-rw-r--r--tests/tls-sni/interimap.remote3
-rw-r--r--tests/tls-sni/remote.conf7
-rw-r--r--tests/tls-sni/t66
-rw-r--r--tests/tls-verify-peer/interimap.remote1
-rw-r--r--tests/tls-verify-peer/t105
-rw-r--r--tests/tls/t11
99 files changed, 1862 insertions, 700 deletions
diff --git a/.gitignore b/.gitignore
index 9dae7e6..e8f355e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
*~
-/doc/*.1
-/doc/*.html
-!/doc/template.html
+*.bak
+/build/
/.pc/
diff --git a/COPYING b/COPYING
index 94a9ed0..e600086 100644
--- a/COPYING
+++ b/COPYING
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
+<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+<https://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/Changelog b/Changelog
index 68e3267..25bba98 100644
--- a/Changelog
+++ b/Changelog
@@ -1,4 +1,189 @@
-interimap (0.5-rc) upstream;
+interimap (0.5.8) upstream;
+
+ + Port tests and documentation to Dovecot 2.4. Running the test suite
+ now require Dovecot 2.3 or later.
+ - Makefile: Replace '$(dir $@)' with '$(@D)'.
+ - tests/*/t: Replace filetype=sh with filetype=bash in vim's hints.
+ - tests/certs/generate: Generate X.509 version 3 CA, and pass CA:TRUE
+ as basic constraint. This fixes the test suite with OpenSSL 3.2 with
+ defaults to X.509v3 and CA:FALSE.
+ - tests: Explicitly pass `-in /dev/stdin` to openssl(1).
+ - tests: Check that that pullimap locks its statefile.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Sat, 26 Apr 2025 18:03:28 +0200
+
+interimap (0.5.7) upstream;
+
+ * interimap: create database with mode 0600 (but don't change mode of
+ existing databases). The file was previously created with mode 0644
+ minus umask restrictions, which for permissive umask(2)s is too open.
+ That being said its parent directory is created with restricted mode
+ 0700 so the impact is limited. pullimap, on the other hand, already
+ used mode 0600 for state file creation.
+ * Major Makefile refactoring: improve DESTDIR= handling, add new
+ targets 'all-nodoc', 'install-nodoc', and 'installcheck'. Also,
+ rename 'test' target to 'check'.
+ * `make install` now installs Net/IMAP/InterIMAP.pm to
+ /usr/local/lib/site_perl by default rather than /usr/local/share/perl5
+ (which is not in @INC as of perl 5.34.0-3 from Debian sid). The
+ installation directory is configurable with sitelib=.
+ * Refactor test harness so one can check the source with `tests/run
+ foo`; what's been built with `INTERIMAP_I=./lib INTERIMAP_PATH=./build
+ ./tests/run foo`, and what's installed with `INTERIMAP_I=""
+ INTERIMAP_PATH=/usr/bin tests/run foo`.
+ * Split interimap and pullimap test suites.
+ + Improve message for missing untagged UIDNEXT responses, which we
+ require but are omitted from some servers.
+ + tests/tls-protocols: downgrade OpenSSL security level to 0, which is
+ required to test TLS version <1.2 on systems with higher security
+ levels, see SSL_CTX_set_security_level(3ssl). Adapted from a patch
+ from <xnox> for Unbuntu.
+ + tests/tls-*: bump Dovecot's ssl_min_protocol to TLSv1.2, which is the
+ default as of dovecot 1:2.3.18+dfsg1-1 from Debian sid.
+ + `make clean` now cleans test certificates and key material.
+ + Add 'use lib "./lib";' to interimap and pullimap, so the programs can
+ be run directly from the source directory. The directory is
+ substituted with $(sitelib) at compile time (and the line is commented
+ out if $(sitelib) is found in @INC).
+ + doc/build.md: update documentation, and add a new section for how to
+ install without root privileges.
+ + Add Documentation=https://guilhem.org/interimap/... URIs to .service
+ files.
+ - Don't hardcode path to interimap/pullimap in .service files, and
+ instead use $(bindir) (expanded at compile time).
+ - tests/certs/generate: redirect known error output to the standard
+ output.
+ - tests/certs/generate: use custom openssl.cnf to avoid depending on
+ the system default.
+ - tests/pullimap: allow easy exclusion of --idle'ing tests.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Sun, 27 Feb 2022 16:24:31 +0100
+
+interimap (0.5.6) upstream;
+
+ - Bump required Net::SSLeay version to 1.86_06 as it's when get_version()
+ was introduced.
+ - doc/template.html: remove type attribute from <style/> element.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Fri, 01 Jan 2021 16:05:53 +0100
+
+interimap (0.5.5) upstream;
+
+ * libinterimap: remove default SSL_protocols value "!SSLv2 !SSLv3
+ !TLSv1 !TLSv1.1" and use the system default instead. As of Debian
+ Buster (OpenSSL 1.1.1) this does not make a difference, however using
+ the system default provides better compatibility with future libssl
+ versions.
+ * libinterimap: deprecate SSL_protocols, obsoleted by new settings
+ SSL_protocol_{min,max}. Using the libssl interface simplifies our
+ protocol black/whitelist greatly; this only allows simple min/max
+ bounds, but holes are arguably not very useful here.
+ * libinterimap: use default locations for trusted CA certificates when
+ neither CAfile nor CApath are set. In particular, OpenSSL's default
+ locations can be overridden by the SSL_CERT_FILE resp. SSL_CERT_DIR
+ environment variables, see SSL_CTX_load_verify_locations(3ssl).
+ * libinterimap: _start_ssl() now fails immediately with OpenSSL <1.1.0.
+ It could in principle still work with earlier versions if the new
+ settings SSL_protocol_{min,max} are not used, however it's cumbersome
+ to do individual checks for specific settings, let alone maintain
+ test coverage with multiple OpenSSL versions.
+ * libinterimap: new option SSL_ciphersuites to set the TLSv1.3
+ ciphersuites; also, clarify that SSL_cipherlist only applies to
+ TLSv1.2 and below, see SSL_CTX_set_cipher_list(3ssl).
+ + `make release`: also bump libinterimap version and pin it in 'use'
+ declarations.
+ + Make error messages more uniform and consistent.
+ - libinterimap: use Net::SSLeay::get_version() to get the protocol
+ version string.
+ - test suite: `mv tests/snippets tests/config`
+ - tests/tls-protocols: use custom OpenSSL configuration file with
+ MinProtocol=None so we can test TLSv1 as well, not just TLSv1.2 and
+ later.
+ - test suite: explicitly set ssl_min_protocol=TLSv1 in the Dovecot
+ configuration file (the default as of 2.3.11.3), hence running TLS
+ tests now require Dovecot 2.3 or later.
+ - documentation: simplify SSL options in the sample configuration files.
+ - README: suggest 'restrict,command="/usr/bin/doveadm exec imap"' as
+ authorized_keys(5) options.
+ - README: suggest ControlPath=$XDG_RUNTIME_DIR/ssh-imap-%C for the SSH
+ transport (note that variable expansion is only available in OpenSSH
+ 8.4 and later).
+ - test suite: ensure we haven't started speaking IMAP when the SSL/TLS
+ handshake is aborted (unless STARTTLS is used to upgrade to
+ connection).
+ - documentation: clarify that known TLS protocol versions depend on the
+ OpenSSL version used.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Sat, 26 Dec 2020 23:11:10 +0100
+
+interimap (0.5.4) upstream;
+
+ * libinterimap: make SSL_verify also checks that the certificate
+ Subject Alternative Name (SAN) or Subject CommonName (CN) matches the
+ hostname or IP literal specified by the 'host' option. Previously it
+ was only checking the chain of trust. This bumps the minimum
+ Net::SSLeay version to 1.83 and OpenSSL version to 1.0.2 (when
+ SSL_verify is used).
+ * libinterimap: add support for the TLS SNI (Server Name Indication)
+ extension, controlled by the new 'SSL_hostname' option. The default
+ value of that option is the value of the 'host' option when it is
+ hostname, and the empty string (which disables SNI) when it is an IP
+ literal.
+ + libinterimap: show the matching pinned SPKI in --debug mode.
+ + test suite: always generate new certificates on `make test`.
+ + test suite: sign all test certificates with the same root CA.
+ + libinterimap: factor out hostname/IP parsing.
+ + document that enclosing 'host' value in square brackets forces its
+ interpretation as an IP literal (hence skips name resolution).
+ + Makefile: new 'release' target; also, change the tag format from
+ upstream/$VERSION to v$VERSION.
+ - documentation: replace example.org with example.net for consistency.
+ - rename 'debian' branch to 'debian/latest' for DEP-14 compliance.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Fri, 11 Dec 2020 11:21:17 +0100
+
+interimap (0.5.3) upstream;
+
+ * libinterimap: SSL_fingerprint now supports a space-separated list of
+ digests to pin, and succeeds if, and only if, the peer certificate
+ SPKI matches one of the pinned digest values. Specifying multiple
+ digest values can key useful in key rollover scenarios and/or when
+ the server supports certificates of different types (for instance
+ RSA+ECDSA).
+ - libinterimap: 'null-stderr' is now ignored when the 'debug' flag is
+ set (the standard error is never sent to /dev/null).
+ - test suite: use a RSA certificate rather than ECDSA.
+ - test suite: new test with a server offering both RSA+ECDSA
+ certificates. This test requires dovecot-imapd 2.2.31 or later.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Wed, 09 Dec 2020 15:32:01 +0100
+
+interimap (0.5.2) upstream;
+
+ - Makefile: remove 'smart' extension from pandoc call to generate
+ manuals (it's no longer supported by pandoc 2.9 which generates \[lq]
+ and \[rq] in the groff output anyway).
+ - libinterimap: fix response injection vulnerability after STARTTLS.
+ For background see https://gitlab.com/muttmua/mutt/-/issues/248 .
+ - libinterimap: abort on PREAUTH greeting received on plaintext
+ connections (set "STARTTLS = NO" to ignore). This is similar to
+ CVE-2020-12398 and CVE-2020-14093.
+ * libinterimap: fail when a capability to ENABLE is missing from the
+ server's CAPABILITY listing.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Mon, 03 Aug 2020 20:50:41 +0200
+
+interimap (0.5.1) upstream;
+
+ + pullimap: also compare RFC 5322 date and envelope information in
+ mailbox comparison tests.
+ + interimap, pullimap: remove `use lib` statement.
+ * Makefile: major refactoring, add install and uninstall targets, honor
+ BUILD_DOCDIR and DESTDIR variables.
+
+ -- Guilhem Moulin <guilhem@fripost.org> Thu, 02 Jul 2020 00:15:03 +0200
+
+interimap (0.5) upstream;
Breaking changes:
* interimap: when matching mailbox names against the 'ignore-mailbox'
@@ -8,7 +193,7 @@ interimap (0.5-rc) upstream;
(regardless of the hierarchy delimiter in use).
Other changes:
- * interimap: the space-speparated list of names and/or patterns in
+ * interimap: the space-separated list of names and/or patterns in
'list-mailbox' can now contain C-style escape sequences (backslash
and hexadecimal escape).
* interimap: fail when two non-INBOX LIST replies return different
@@ -16,7 +201,7 @@ interimap (0.5-rc) upstream;
happen if mailboxes from different namespaces are being listed. The
workaround here is to run a new interimap instance for each
namespace.
- * libinterimap: in tunnel mode, use a socketpair rather than two pipes
+ * libinterimap: in tunnel mode, use a socket pair rather than two pipes
for IPC between the interimap and the IMAP server. Also, use
SOCK_CLOEXEC to save an fcntl() call when setting the close-on-exec
flag on the socket.
@@ -79,7 +264,7 @@ interimap (0.5-rc) upstream;
- libinterimap: use directories relative to $HOME for the XDG
environment variables default values. Previously getpwuid() was
called to determine the user's home directory, while the XDG
- specification explicitely mentions $HOME. Conveniently our docs
+ specification explicitly mentions $HOME. Conveniently our docs
always mentioned ~/, which on POSIX-compliant systems expands to the
value of the variable HOME. (Cf. Shell and Utilities volume of
POSIX.1-2017, sec. 2.6.1.)
@@ -93,7 +278,7 @@ interimap (0.5-rc) upstream;
- libinterimap: push_flag_updates(): ignore UIDs for which no untagged
FETCH response was received.
- libinterimap: push_flag_updates(): don't ignores received updates (by
- another client) to a superset of the desigred flag list.
+ another client) to a superset of the desired flag list.
- libinterimap: avoid sending large UID EXPUNGE|FETCH|STORE commands as
they might exceed the server's max acceptable command size; these
commands are now split into multiple (sequential) commands when their
@@ -103,7 +288,7 @@ interimap (0.5-rc) upstream;
This is a also a workaround for a bug in Dovecot 2.3.4:
https://dovecot.org/pipermail/dovecot/2019-November/117522.html
- interimap: for the reason explained above, limit number of messages
- to 128 per APPEND command (only on servers advertizing MULTIAPPEND,
+ to 128 per APPEND command (only on servers advertising MULTIAPPEND,
for other servers the number remains 1).
- interimap: gracefully ignore messages with a NIL RFC822 attribute.
- pullimap: treat messages with a NIL RFC822 attribute as empty.
diff --git a/Makefile b/Makefile
index b232602..5ecff73 100644
--- a/Makefile
+++ b/Makefile
@@ -1,39 +1,117 @@
-all: manual
+srcdir ?= .
+builddir ?= build
+prefix ?= /usr/local
+exec_prefix ?= $(prefix)
+bindir ?= $(exec_prefix)/bin
+libdir ?= $(exec_prefix)/lib
+sitelib ?= $(libdir)/site_perl
+datarootdir ?= $(prefix)/share
+mandir ?= $(datarootdir)/man
+man1dir ?= $(mandir)/man1
+systemd_userunitdir ?= $(libdir)/systemd/user
-MANUALS = $(patsubst %.md,%,$(wildcard ./doc/*.[1-9].md))
-manual: $(MANUALS)
+CSS ?= /usr/share/javascript/bootstrap4/css/bootstrap.css
+HTML_TEMPLATE ?= $(srcdir)/doc/template.html
+
+PROGRAMS = $(addprefix $(builddir)/,interimap pullimap)
+HTML_FILES = $(patsubst $(srcdir)/doc/%.md,$(builddir)/doc/%.html,$(wildcard $(srcdir)/doc/*.md))
+MANUAL_FILES = $(patsubst $(srcdir)/doc/%.md,$(builddir)/doc/%,$(wildcard $(srcdir)/doc/*.[1-9].md))
+SERVICE_FILES = $(patsubst $(srcdir)/%.service,$(builddir)/%.service,$(wildcard $(srcdir)/*.service))
+
+all: all-nodoc manual
+all-nodoc: $(PROGRAMS) $(SERVICE_FILES)
+
+doc: manual html
+manual: $(MANUAL_FILES)
+html: $(HTML_FILES)
+
+$(PROGRAMS): $(builddir)/%: $(srcdir)/%
+ @mkdir -vp -- $(@D)
+ perl -Te "print \"\$$_\\0\" foreach @INC;" | grep -Fxzq -e "$(sitelib)" && prefix="#" || prefix=""; \
+ sed -r "0,/^(use\\s+\lib\\s+)([\"'])[^\"']*\\2\\s*;/ s||$$prefix\\1\"$(sitelib)\";|" <"$<" >"$@"
+ chmod --reference="$<" -- "$@"
# upper case the headers and remove the links
-$(MANUALS): %: %.md
- pandoc -f markdown -t json -- "$<" | ./pandoc2man.jq | pandoc -s -f json -t man+smart -o "$@"
+$(MANUAL_FILES): $(builddir)/doc/%: $(srcdir)/doc/%.md
+ @mkdir -vp -- $(@D)
+ pandoc -f markdown -t json -- "$<" | $(srcdir)/pandoc2man.jq | pandoc -s -f json -t man -o "$@"
-test:
- @./tests/run-all
+$(SERVICE_FILES): $(builddir)/%.service: $(srcdir)/%.service
+ @mkdir -vp -- $(@D)
+ sed "s|@bindir@|$(bindir)|" <"$<" >"$@"
-HTML_ROOTDIR ?= ./doc
-CSS ?= /usr/share/javascript/bootstrap/css/bootstrap.min.css
-HTML_TEMPLATE ?= ./doc/template.html
+testcerts:
+ $(srcdir)/tests/certs/generate
-HTML_FILES = $(addprefix $(HTML_ROOTDIR)/,$(patsubst ./doc/%.md,%.html,$(wildcard ./doc/*.md)))
-html: $(HTML_FILES)
+check: check-interimap check-pullimap
+check-interimap: $(builddir)/interimap testcerts
+ INTERIMAP_I=$(srcdir)/lib INTERIMAP_PATH=$(builddir) $(srcdir)/tests/run-all interimap.list
+check-pullimap: $(builddir)/pullimap testcerts
+ INTERIMAP_I=$(srcdir)/lib INTERIMAP_PATH=$(builddir) $(srcdir)/tests/run-all pullimap.list
+
+installcheck: installcheck-interimap installcheck-pullimap
+installcheck-interimap: testcerts
+ INTERIMAP_I="" INTERIMAP_PATH=$(bindir) $(srcdir)/tests/run-all interimap.list
+installcheck-pullimap: testcerts
+ INTERIMAP_I="" INTERIMAP_PATH=$(bindir) $(srcdir)/tests/run-all pullimap.list
+
+release:
+ @if ! git -C $(srcdir) diff --quiet HEAD -- Changelog interimap pullimap lib/Net/IMAP/InterIMAP.pm; then \
+ echo "Dirty state, refusing to release!" >&2; \
+ exit 1; \
+ fi
+ VERS=$$(dpkg-parsechangelog -l $(srcdir)/Changelog -SVersion 2>/dev/null) && \
+ if git -C $(srcdir) rev-parse -q --verify "refs/tags/v$$VERS" >/dev/null; then echo "tag exists" 2>/dev/null; exit 1; fi && \
+ sed -ri "0,/^( -- .*) .*/ s//\\1 $(shell date -R)/" $(srcdir)/Changelog && \
+ sed -ri "0,/^(our\\s+\\\$$VERSION\\s*=\\s*)'[0-9.]+'\\s*;/ s//\\1'$$VERS';/" \
+ -- $(srcdir)/interimap $(srcdir)/pullimap && \
+ sed -ri "0,/^(package\\s+Net::IMAP::InterIMAP\\s+)v[0-9.]+\\s*;/ s//\\1v$$VERS;/" \
+ -- $(srcdir)/lib/Net/IMAP/InterIMAP.pm && \
+ sed -ri "0,/^(use\\s+Net::IMAP::InterIMAP\\s+)[0-9.]+(\\s|\\$$)/ s//\\1$$VERS\\2/" \
+ -- $(srcdir)/interimap $(srcdir)/pullimap && \
+ git -C $(srcdir) commit -m "Prepare new release v$$VERS." \
+ -- Changelog interimap pullimap lib/Net/IMAP/InterIMAP.pm && \
+ git -C $(srcdir) tag -sm "Release version $$VERS" "v$$VERS"
-## CSS="https://guilhem.org/static/css/bootstrap.min.css" HTML_ROOTDIR="$XDG_RUNTIME_DIR/Downloads" make html
-$(HTML_ROOTDIR)/%.html: ./doc/%.md $(HTML_TEMPLATE)
- mtime="$$(git --no-pager log -1 --pretty="format:%ct" -- "$<" 2>/dev/null)"; \
+$(HTML_FILES): $(builddir)/doc/%.html: $(srcdir)/doc/%.md $(HTML_TEMPLATE)
+ @mkdir -vp -- $(@D)
+ mtime="$$(git -C $(srcdir) --no-pager log -1 --pretty="format:%ct" -- "$<" 2>/dev/null)"; \
[ -n "$$mtime" ] || mtime="$$(date +%s -r "$<")"; \
- [ "$<" = "doc/index.md" ] && parent="" || parent="./index.html"; \
pandoc -sp -f markdown -t html+smart --css=$(CSS) --template=$(HTML_TEMPLATE) \
--variable=date:"$$(LC_TIME=C date +"Last modified on %a, %d %b %Y at %T %z" -d @"$$mtime")" \
--variable=keywords:"interimap" \
--variable=lang:"en" \
- --variable=parent:"$$parent" \
+ --variable=parent:"$(if $(filter $@,$(builddir)/doc/index.html),,./index.html)" \
--output="$@" -- "$<"
-doc: manual html
+INSTALL ?= install
+INSTALL_PROGRAM ?= $(INSTALL)
+INSTALL_DATA ?= $(INSTALL) -m0644
+
+install: install-nodoc
+ -$(INSTALL_DATA) -vDt $(DESTDIR)$(man1dir) $(builddir)/doc/interimap.1 $(builddir)/doc/pullimap.1
+ $(INSTALL_DATA) -vDt $(DESTDIR)$(datarootdir)/doc/pullimap $(srcdir)/pullimap.sample
+ $(INSTALL_DATA) -vDt $(DESTDIR)$(datarootdir)/doc/interimap $(srcdir)/interimap.sample \
+ $(srcdir)/doc/getting-started.md $(srcdir)/doc/multi-account.md $(srcdir)/README
+
+install-nodoc: all-nodoc
+ $(INSTALL_PROGRAM) -vDt $(DESTDIR)$(bindir) $(builddir)/interimap $(builddir)/pullimap
+ $(INSTALL_DATA) -vDT $(srcdir)/lib/Net/IMAP/InterIMAP.pm $(DESTDIR)$(sitelib)/Net/IMAP/InterIMAP.pm
+ $(INSTALL_DATA) -vDt $(DESTDIR)$(systemd_userunitdir) $(SERVICE_FILES)
-install:
+uninstall:
+ rm -vf -- $(DESTDIR)$(bindir)/interimap $(DESTDIR)$(man1dir)/interimap.1 $(DESTDIR)$(systemd_userunitdir)/interimap*.service
+ rm -vf -- $(DESTDIR)$(bindir)/pullimap $(DESTDIR)$(man1dir)/pullimap.1 $(DESTDIR)$(systemd_userunitdir)/pullimap*.service
+ rm -vf -- $(DESTDIR)$(sitelib)/Net/IMAP/InterIMAP.pm
+ rm -rvf -- $(DESTDIR)$(datarootdir)/doc/interimap $(DESTDIR)$(datarootdir)/doc/pullimap
clean:
- rm -f $(MANUALS) $(HTML_FILES)
+ rm -vf -- $(PROGRAMS) $(MANUAL_FILES) $(HTML_FILES) $(SERVICE_FILES)
+ rm -vf -- $(srcdir)/tests/certs/*.key $(srcdir)/tests/certs/*.crt $(srcdir)/tests/certs/*.pem
+ -rmdir -vp --ignore-fail-on-non-empty -- $(builddir)/doc
-.PHONY: all manual html doc test install clean
+.PHONY: all all-nodoc manual html doc release testcerts \
+ check check-interimap check-pullimap \
+ install install-nodoc \
+ installcheck installcheck-interimap installcheck-pullimap \
+ uninstall clean
diff --git a/README b/README
index fbc4ed7..85562d4 100644
--- a/README
+++ b/README
@@ -1,54 +1,51 @@
InterIMAP is a fast bidirectional synchronization program for QRESYNC-capable
IMAP4rev1 servers. PullIMAP retrieves messages a remote IMAP mailbox and
-deliver them to an SMTP session. Visit https://guilhem.org/interimap
-for more information.
+deliver them to an SMTP session. Visit https://guilhem.org/interimap for more
+information.
-_______________________________________________________________________
+______________________________________________________________________________
-Compared to IMAP-to-Maildir synchronization solutions like OfflineIMAP,
-adding an IMAP server between the Maildir storage and the MUA saves
-loads of readdir(2) system calls and other File System quirks; moreover
-the abstraction layer offered by the IMAP server makes the MUA and
-synchronization program agnostic to the storage backend (Maildir, mbox,
-dbox,...) in use.
+Compared to IMAP-to-Maildir synchronization solutions like OfflineIMAP, adding
+an IMAP server between the Maildir storage and the MUA saves loads of
+readdir(2) system calls and other File System quirks; moreover the abstraction
+layer offered by the IMAP server makes the MUA and synchronization program
+agnostic to the storage backend (Maildir, mbox, dbox,...) in use.
IMAP synchronization of a mailbox is usually two-folds: 1/ detect and
-propagate changes (flag updates and message deletions) to existing
-messages, then 2/ copy the new messages. The naive way to perform the
-first step is to issue a FETCH command to list all messages in the
-mailbox along with their flags and UIDs, causing heavy network usage.
-Instead, InterIMAP takes advantage of the QRESYNC extension from
-[RFC7162] to perform stateful synchronization: querying changes since
-the last synchronization only gives a phenomenal performance boost and
-drastically reduces the network traffic.
+propagate changes (flag updates and message deletions) to existing messages,
+then 2/ copy the new messages. The naive way to perform the first step is to
+issue a FETCH command to list all messages in the mailbox along with their
+flags and UIDs, causing heavy network usage. Instead, InterIMAP takes
+advantage of the QRESYNC extension from [RFC7162] to perform stateful
+synchronization: querying changes since the last synchronization only gives a
+phenomenal performance boost and drastically reduces the network traffic.
-For convenience reasons servers must also support LIST-EXTENDED
-[RFC5258], LIST-STATUS [RFC5819] and UIDPLUS [RFC4315]. Other supported
-extensions are:
- * LITERAL+ [RFC2088] non-synchronizing literals (recommended),
- * MULTIAPPEND [RFC3502] (recommended),
- * COMPRESS=DEFLATE [RFC4978] (recommended),
- * SASL-IR [RFC4959] SASL Initial Client Response, and
+For convenience reasons servers must also support LIST-EXTENDED [RFC5258],
+LIST-STATUS [RFC5819] and UIDPLUS [RFC4315]. Other supported extensions are:
+
+ * LITERAL+ [RFC2088] non-synchronizing literals (recommended);
+ * MULTIAPPEND [RFC3502] (recommended);
+ * COMPRESS=DEFLATE [RFC4978] (recommended);
+ * SASL-IR [RFC4959] SASL Initial Client Response; and
* UNSELECT [RFC3691].
-_______________________________________________________________________
+______________________________________________________________________________
-IMAP traffic is mostly text (beside message bodies perhaps) hence
-compresses pretty well: enabling compression can save a great amount of
-network resources.
+IMAP traffic is mostly text (beside message bodies perhaps) hence compresses
+pretty well: enabling compression can save a great amount of network
+resources.
However establishing an SSL/TLS connection (type=imaps, or type=imap and
STARTTLS=YES) yields a small overhead due to the SSL/TLS handshake.
On the other hand if SSH access is allowed on the remote server, one can
-tunnel the IMAP traffic through SSH and use OpenSSH's ControlPersist
-feature to save most of the cryptographic overhead (at the expense of a
-local 'ssh' process and a remote 'imap' process). Moreover if the IMAP
-user is a valid UNIX user it is possible to use pre-authentication on
-the remote server as well, which saves the extra round trip caused by
-the AUTHENTICATE command. For instance the following configuration
-snippet saves bandwidth and brings a significant speed gain compared to
-type=imaps.
+tunnel the IMAP traffic through SSH and use OpenSSH's ControlPersist feature
+to save most of the cryptographic overhead (at the expense of a local 'ssh'
+process and a remote 'imap' process). Moreover if the IMAP user is a valid
+UNIX user it is possible to use pre-authentication on the remote server as
+well, which saves the extra round trip caused by the AUTHENTICATE command.
+For instance the following configuration snippet saves bandwidth and brings a
+significant speed gain compared to type=imaps.
local: $XDG_CONFIG_HOME/interimap/config:
[remote]
@@ -59,7 +56,7 @@ type=imaps.
Host imap.example.net
IdentityFile ~/.ssh/id-interimap
IdentitiesOnly yes
- ControlPath /run/shm/%u@%n
+ ControlPath ${XDG_RUNTIME_DIR}/ssh-imap-%C
ControlMaster auto
ControlPersist 10m
StrictHostKeyChecking yes
@@ -69,17 +66,17 @@ type=imaps.
Compression yes
remote: ~user/.ssh/authorized_keys:
- command="/usr/lib/dovecot/imap",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-... id-interimap
+ restrict,command="/usr/bin/doveadm exec imap" ssh-[…] id-interimap
-However for long-lived connections (using the --watch command-line
-option), the TLS overhead becomes negligible hence the advantage offered
-by the OpenSSH ControlPersist feature is not obvious. Furthermore if
-the remote server supports the IMAP COMPRESS extension [RFC4978], adding
-compress=DEFLATE to the configuration can also greatly reduce bandwidth
-usage with regular INET sockets (type=imaps or type=imap).
+However for long-lived connections (using the --watch command-line option),
+the TLS overhead becomes negligible hence the advantage offered by the OpenSSH
+ControlPersist feature is not obvious. Furthermore if the remote server
+supports the IMAP COMPRESS extension [RFC4978], adding compress=DEFLATE to the
+configuration can also greatly reduce bandwidth usage with regular INET
+sockets (type=imaps or type=imap).
-_______________________________________________________________________
+______________________________________________________________________________
-InterIMAP is Copyright© 2015-2018 Guilhem Moulin ⟨guilhem@fripost.org⟩,
-and licensed for use under the GNU General Public License version 3 or
-later. See ‘COPYING’ for specific terms and distribution information.
+InterIMAP is Copyright © 2015-2022 Guilhem Moulin ⟨guilhem@fripost.org⟩, and
+is licensed for use under the GNU General Public License version 3 or later.
+See ‘COPYING’ for specific terms and distribution information.
diff --git a/benchmark/dovecot.conf b/benchmark/dovecot.conf
index 55301d9..68cc40e 100644
--- a/benchmark/dovecot.conf
+++ b/benchmark/dovecot.conf
@@ -1,15 +1,20 @@
+dovecot_config_version = 2.4.0
+dovecot_storage_version = 2.4.0
+
log_path = /dev/shm/mail.log
-mail_home = /dev/shm/vmail/%u
-mail_location = mdbox:~/mail
+mail_home = /dev/shm/vmail/%{user | username | lower}
+mail_driver = mdbox
+mail_path = ~/mail
+mailbox_list_index = yes
ssl = no
listen = 127.0.0.1
-namespace {
+namespace inbox {
inbox = yes
separator = /
}
-# https://wiki.dovecot.org/HowTo/Rootless
+# https://doc.dovecot.org/latest/core/config/rootless.html#rootless-installation
base_dir = /dev/shm/dovecot/run
default_internal_user = nobody
default_internal_group = nogroup
@@ -25,20 +30,18 @@ service stats {
chroot =
}
-passdb {
- args = scheme=PLAIN username_format=%u /dev/shm/dovecot/users
+passdb passwd-file {
driver = passwd-file
+ default_password_scheme = plain
+ passwd_file_path = /dev/shm/dovecot/users
}
-userdb {
- args = username_format=%u /dev/shm/dovecot/users
+userdb passwd-file {
driver = passwd-file
+ passwd_file_path = /dev/shm/dovecot/users
}
-protocols = imap
-
-mail_plugins = zlib
-protocol imap {
- mail_plugins = imap_zlib
+protocols {
+ imap = yes
}
service imap-login {
diff --git a/benchmark/random_maildir.pl b/benchmark/random_maildir.pl
index 363eb41..d75fa4f 100755
--- a/benchmark/random_maildir.pl
+++ b/benchmark/random_maildir.pl
@@ -15,7 +15,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
#----------------------------------------------------------------------
use warnings;
diff --git a/benchmark/run b/benchmark/run
index 4a83c68..dd30bdc 100755
--- a/benchmark/run
+++ b/benchmark/run
@@ -140,15 +140,19 @@ jail /usr/sbin/dovecot -c"/dev/shm/dovecot/config"
install -onobody -gnogroup -m0600 /dev/null \
"$ROOTDIR/dev/shm/dovecot/users"
PASSWORD="$(xxd -l16 -p </dev/urandom)"
-printf "%s:%s:::::\\n" "user" "$PASSWORD" \
+printf "%s:%s::::%s:\\n" "user" "$PASSWORD" "/dev/shm/vmail/user" \
>"$ROOTDIR/dev/shm/dovecot/users"
# install user configuration for Dovecot, interimap, and offlineimap
cat >"$ROOTDIR/dev/shm/nobody/.dovecot.conf" <<-EOF
+ dovecot_config_version = 2.4.0
+ dovecot_storage_version = 2.4.0
log_path = /dev/null
- mail_home = /dev/shm/nobody
- mail_location = maildir:~/Maildir
ssl = no
+ mail_home = /dev/shm/nobody
+ mail_driver = maildir
+ mail_path = ~/Maildir
+ mailbox_list_index = yes
EOF
install -onobody -gnogroup -Dm0700 --directory \
@@ -198,9 +202,10 @@ cat >"$ROOTDIR/dev/shm/nobody/.offlineimaprc" <<-EOF
EOF
# install interimap's development version
-install -oroot -groot -m0755 -Dt "/$ROOTDIR/usr/bin" \
+install -oroot -groot -m0755 -Dt "$ROOTDIR/usr/bin" \
./interimap ./benchmark/random_maildir.pl
-install -oroot -groot -Dm0644 \
+sed -ri "0,/^(use\\s+\lib\\s+)([\"'])[^\"']*\\2\\s*;/ s||#&|" -- "$ROOTDIR/usr/bin/interimap"
+install -oroot -groot -m0644 -DT \
./lib/Net/IMAP/InterIMAP.pm "$ROOTDIR/usr/share/perl5/Net/IMAP/InterIMAP.pm"
# create a random mail store at mdbox:~/mail.back
@@ -221,18 +226,19 @@ prepare() {
done
# convert to mdbox
- jail doveadm -c"/dev/shm/dovecot/config" -omail_location="maildir:~/maildir" \
- sync "mdbox:~/mail.back"
+ jail doveadm -c"/dev/shm/dovecot/config" -omail_driver="maildir" -omail_path="~/maildir" \
+ sync --no-userdb-lookup "mdbox:~/mail.back"
jail rm -rf -- "$maildir"
# expunge 20% and purge
for m in "${!MAILBOXES[@]}"; do
n="${MAILBOXES["$m"]}"
seqs="$(shuf -n $((n/5)) -i"1-$n")"
- jail doveadm -c"/dev/shm/dovecot/config" -omail_location="mdbox:~/mail.back" \
- expunge mailbox "$m" "${seqs//$'\n'/,}"
+ jail doveadm -c"/dev/shm/dovecot/config" -omail_driver="mdbox" -omail_path="~/mail.back" \
+ expunge --no-userdb-lookup mailbox "$m" "${seqs//$'\n'/,}"
done
- jail doveadm -c"/dev/shm/dovecot/config" -omail_location="mdbox:~/mail.back" purge
+ jail doveadm -c"/dev/shm/dovecot/config" -omail_driver="mdbox" -omail_path="~/mail.back" \
+ purge --no-userdb-lookup
}
# populate a clientn from backup mailstore mdbox:~/mail.back (copied to
@@ -249,8 +255,8 @@ populate() {
# force dovecot to index and compute the state, otherwise the first
# thing to query might be disadvantaged
- jail doveadm -c"/dev/shm/dovecot/config" index "INBOX"
- jail doveadm -c"/dev/shm/dovecot/config" mailbox status "all" "*" >/dev/null
+ jail doveadm -c"/dev/shm/dovecot/config" index --no-userdb-lookup "INBOX" 2>/dev/null
+ jail doveadm -c"/dev/shm/dovecot/config" mailbox status --no-userdb-lookup "all" "*" >/dev/null
u="local"
# initial configuration
@@ -394,9 +400,9 @@ activity2() {
deliver "mailbox2" <"$ROOTDIR/tmp/msg2"
deliver "mailbox3" <"$ROOTDIR/tmp/msg3"
# intentionally modify the remote only because not all local backend speak IMAP
- jail doveadm -c"/dev/shm/dovecot/config" expunge mailbox "mailbox3" "1:2"
- jail doveadm -c"/dev/shm/dovecot/config" expunge mailbox "mailbox4" "1,3"
- jail doveadm -c"/dev/shm/dovecot/config" expunge mailbox "mailbox5" "*"
+ jail doveadm -c"/dev/shm/dovecot/config" expunge --no-userdb-lookup mailbox "mailbox3" "1:2"
+ jail doveadm -c"/dev/shm/dovecot/config" expunge --no-userdb-lookup mailbox "mailbox4" "1,3"
+ jail doveadm -c"/dev/shm/dovecot/config" expunge --no-userdb-lookup mailbox "mailbox5" "*"
}
for n in 100 1000 10000; do
@@ -488,14 +494,14 @@ while [ $(date +%s) -lt $timeout ]; do
n="$(shuf -n1 -i1-100)"
if [ $n -le 5 ]; then
# expunge a random message on the remote
- read guid uid < <(jail doveadm -c"/dev/shm/dovecot/config" search all | shuf -n1)
- jail doveadm -c"/dev/shm/dovecot/config" expunge mailbox-guid "$guid" uid "$uid"
+ read guid uid < <(jail doveadm -c"/dev/shm/dovecot/config" search --no-userdb-lookup all | shuf -n1)
+ jail doveadm -c"/dev/shm/dovecot/config" expunge --no-userdb-lookup mailbox-guid "$guid" uid "$uid"
fi
n="$(shuf -n1 -i1-100)"
if [ $n -le 10 ]; then
# mark a random message as seen
- read guid uid < <(jail doveadm -c"/dev/shm/dovecot/config" search all | shuf -n1)
- jail doveadm -c"/dev/shm/dovecot/config" flags add "\\Seen" mailbox-guid "$guid" uid "$uid"
+ read guid uid < <(jail doveadm -c"/dev/shm/dovecot/config" search --no-userdb-lookup all | shuf -n1)
+ jail doveadm -c"/dev/shm/dovecot/config" flags add --no-userdb-lookup "\\Seen" mailbox-guid "$guid" uid "$uid"
fi
sleep $step
done
diff --git a/doc/benchmark.md b/doc/benchmark.md
index 72f51a4..3b58e10 100644
--- a/doc/benchmark.md
+++ b/doc/benchmark.md
@@ -48,8 +48,8 @@ by placing packet counters on the interface.
[OfflineIMAP]: https://www.offlineimap.org/
[benchmark-script]: https://git.guilhem.org/interimap/plain/benchmark/run
[Dovecot]: https://dovecot.org
-[dbox]: https://wiki.dovecot.org/MailboxFormat/dbox
-[maildir]: https://wiki.dovecot.org/MailboxFormat/Maildir
+[dbox]: https://doc.dovecot.org/latest/core/config/mailbox_formats/dbox.html
+[maildir]: https://doc.dovecot.org/latest/core/config/mailbox_formats/maildir.html
-----------------------------------------------------------------------
diff --git a/doc/build.md b/doc/build.md
index d704f71..b40760e 100644
--- a/doc/build.md
+++ b/doc/build.md
@@ -1,7 +1,7 @@
% Build instructions
% [Guilhem Moulin](mailto:guilhem@fripost.org)
-On Debian 9 (codename *Stretch*) and later, installing [`interimap`(1)]
+On Debian 10 (codename *Buster*) and later, installing [`interimap`(1)]
is a single command away:
$ sudo apt install interimap
@@ -24,7 +24,7 @@ following Perl modules:
* [`Getopt::Long`](https://perldoc.perl.org/Getopt/Long.html) (*core module*)
* [`MIME::Base64`](https://perldoc.perl.org/MIME/Base64.html) (*core module*) — if authentication is required
* [`List::Util`](https://perldoc.perl.org/List/Util.html) (*core module*)
- * [`Net::SSLeay`](https://metacpan.org/pod/Net::SSLeay) ≥1.73
+ * [`Net::SSLeay`](https://metacpan.org/pod/Net::SSLeay) ≥1.86_06
* [`POSIX`](https://perldoc.perl.org/POSIX.html) (*core module*)
* [`Socket`](https://perldoc.perl.org/Socket.html) (*core module*)
* [`Time::HiRes`](https://perldoc.perl.org/Time/HiRes.html) (*core module*) — if `logfile` is set
@@ -38,10 +38,10 @@ Additional packages are required in order to run the test suite:
$ sudo apt install dovecot-imapd dovecot-lmtpd openssl procps sqlite3 xxd
<!-- -->
- $ make test
+ $ make check
-(The test suite also needs to bind to TCP ports 10024, 10143 and 10993
-on the loopback interface.)
+(Note also that the test suite needs to bind to TCP ports 10024, 10143
+and 10993 on the loopback interface.)
Generate documentation
======================
@@ -51,32 +51,53 @@ Yet another set of packages is needed to generate the documentation:
$ sudo apt install jq pandoc
Run `` `make manual` `` (or just `` `make` ``) in order to generate the
-manpages. You'll find them at `doc/*.[1-9]`. Use for instance `` `man
--l doc/interimap.1` `` in order to read your copy of the [`interimap`(1)]
-manpage.
+manuals. You'll find them at `build/doc/*.[1-9]`. Then use for
+instance `` `man -l build/doc/interimap.1` `` to read your local copy of
+the [`interimap`(1)] manual.
-The HTML documentation can be built with `` `make html` ``. HTML files
-are generated alongside their Markdown source by default, but you can
-choose another target directory using the `HTML_ROOTDIR` environment
-variable (the value of which defaults to `./doc`). Moreover the
-[`libjs-bootstrap`](https://tracker.debian.org/libjs-bootstrap) is
-needed by default for the local CSS file; this can be controlled with
-the `CSS` environment variable (the value of which defaults to
-`/usr/share/javascript/bootstrap/css/bootstrap.min.css`).
+The HTML documentation can be built with `` `make html` ``. By default
+HTML files are generated at `build/doc/*.html`, and a local CSS file is
+used — namely `/usr/share/javascript/bootstrap4/css/bootstrap.css`,
+shipped the by [`libjs-bootstrap`](https://tracker.debian.org/libjs-bootstrap)
+package. But this is configurable: use for instance
-For instance, use
+ $ make html builddir="$XDG_RUNTIME_DIR/interimap" \
+ CSS="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css"
- $ CSS="https://guilhem.org/static/css/bootstrap.min.css" \
- HTML_ROOTDIR="$XDG_RUNTIME_DIR/interimap" \
- make html
+to generate the HTML documentation under `$XDG_RUNTIME_DIR/interimap/doc`
+using a remote CSS file.
-to generate the HTML documentation under directory `$XDG_RUNTIME_DIR/interimap`
-(which needs to exist) using a remote CSS file.
-
-The `doc` target generates all documentation, manpages as well as HTML
+The `doc` target generates all documentation, manuals as well as HTML
pages.
+Installation without root privileges
+====================================
+
+By default `` `make install` `` installs [`interimap`(1)] under
+`/usr/local`, hence requires root privileges. However another prefix
+can be used in order to perform the (un)installation as an unprivileged
+user. For instance
+
+ $ install -m0700 -vd ${XDG_DATA_HOME:-~/.local/share}/interimap
+ $ make install-nodoc \
+ prefix=$HOME/.local \
+ sitelib=${XDG_DATA_HOME:-~/.local/share}/interimap/lib \
+ systemd_userunitdir=${XDG_DATA_HOME:-~/.local/share}/systemd/user
+
+skips documentation and installs
+
+ * executables into `~/.local/bin` (instead of `/usr/local/bin`);
+ * libraries into `$XDG_DATA_HOME/interimap/lib` or `~/.local/share/interimap/lib`
+ (instead of `/usr/local/lib/site_perl`); and
+ * [systemd user unit files][`systemd.unit`(5)] into `$XDG_DATA_HOME/systemd/user`
+ or `~/.local/share/systemd/user` (instead of `/usr/local/lib/systemd/user`).
+
+Note that for uninstallation one must call `` `make uninstall prefix=…` ``
+with the very same assignment(s) used for installation.
+
+[`systemd.unit`(5)]: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
+
Build custom Debian packages
============================
@@ -84,12 +105,12 @@ Debian GNU/Linux users can also use [`gbp`(1)] from
[`git-buildpackage`](https://tracker.debian.org/pkg/git-buildpackage) in
order to build their own packages:
- $ git checkout debian
+ $ git checkout debian/latest
$ gbp buildpackage
Alternatively, for the development version:
- $ git checkout debian
+ $ git checkout debian/latest
$ git merge master
$ gbp buildpackage --git-force-create --git-upstream-tree=BRANCH
diff --git a/doc/development.md b/doc/development.md
index 52ebf90..f739dc0 100644
--- a/doc/development.md
+++ b/doc/development.md
@@ -15,14 +15,18 @@ Dovecot configuration
=====================
Create a file `$BASEDIR/dovecot.conf`, which will be used as
-configuration for the various Dovecot commands (the system configuration
-will be skipped).
+configuration for the various Dovecot 2.4 commands (the system
+configuration will be skipped).
$ cat >"$BASEDIR/dovecot.conf" <<-EOF
- log_path = "$BASEDIR/dovecot.log"
- ssl = no
- mail_home = "$BASEDIR/%u"
- mail_location = maildir:~/Mail
+ dovecot_config_version = 2.4.0
+ dovecot_storage_version = 2.4.0
+ log_path = "$BASEDIR/dovecot.log"
+ ssl = no
+
+ mail_home = "$BASEDIR/%{user | username | lower}"
+ mail_driver = maildir
+ mail_path = ~/Mail
EOF
Here are some details on the above:
@@ -36,18 +40,17 @@ Here are some details on the above:
`mail_home`
: Dovecot needs the name of the user to (pre-)authenticate. It is shown
- in the greeting line, and also used in [`%`-variable] expansion.
+ in the greeting line, and also used in [settings variables] expansion.
Several [`doveadm`(1)] sub-commands have a `-u` (or `-d`) option which
- can be used to determine the username. When this option is not set,
- the username is taken from the `USER` environment variable. If that
- environment variable is unset as well, then the return string of
- [`getlogin`(3)] is used.
+ can be used to determine the username. To avoid performing the userdb
+ lookup one can pass `--no-userdb-lookup` instead, in which case the
+ username is taken from the `USER` environment variable.
Similarly, the user's home directory is used in (`~`- and)
- [`%`-variable] expansion. It's taken from the `HOME` environment
- variable when the `mail_home` setting is left unset in the Dovecot
- configuration (and not overridden by the [user database][User
- Databases].
+ [settings variables] expansion. It's taken from the `HOME`
+ environment variable when the `mail_home` setting is left unset in
+ the Dovecot configuration (and not overridden by the
+ [user database][User Databases].
`mail_home` can therefore be left unset if the `HOME` environment
variable is consistently set to `$BASEDIR/$USER`. However it's
@@ -55,14 +58,17 @@ Here are some details on the above:
command run in a non-curated environment might mess up with your own
mail storage…
-`mail_location`
+`mail_driver`
- : The user's mail storage resides — in [Maildir] format — in a directory
- `Mail` under their home directory. This is enough if you're fine with
- the default IMAP hierarchy delimiter (which depends on the mail format)
- is used, and if you need a single [IMAP namespace][RFC 2342]. For more
- complex setups you'll need one or more [`namespace {…}` block][Dovecot
- Namespaces].
+ : Use the [Maildir] format for mail storage..
+
+`mail_path`:
+
+ : The user's mail storage resides in a directory `Mail` under their
+ home directory. This is enough if you're fine with the default IMAP
+ hierarchy delimiter (which depends on the mail format) is used, and
+ if you need a single [IMAP namespace][RFC 2342]. For more complex
+ setups you'll need one or more [`namespace {…}` block][Dovecot Namespaces].
Mail storage access
===================
@@ -72,7 +78,7 @@ pre-authenticated [IMAP4rev1] in the test environment for username
`testuser`, list mailboxes, and exit, run:
$ env -i PATH="/usr/bin:/bin" USER="testuser" \
- doveadm -c "$BASEDIR/dovecot.conf" exec imap
+ doveadm -c "$BASEDIR/dovecot.conf" exec imap
S: * PREAUTH [CAPABILITY IMAP4rev1 …] Logged in as testuser
C: a LIST "" "*"
S: * LIST (\HasNoChildren) "." INBOX
@@ -88,10 +94,10 @@ the latter to create a mailbox `foo`, add a sample message to it, and
finally mark it as `\Seen`.
$ env -i PATH="/usr/bin:/bin" USER="testuser" \
- doveadm -c "$BASEDIR/dovecot.conf" mailbox create "foo"
+ doveadm -c "$BASEDIR/dovecot.conf" mailbox create --no-userdb-lookup "foo"
<!-- -->
$ env -i PATH="/usr/bin:/bin" USER="testuser" HOME="$BASEDIR/testuser" \
- doveadm -c "$BASEDIR/dovecot.conf" exec dovecot-lda -e -m "foo" <<-EOF
+ doveadm -c "$BASEDIR/dovecot.conf" exec dovecot-lda -e -m "foo" <<-EOF
From: <sender@example.net>
To: <recipient@example.net>
Subject: Hello world!
@@ -102,7 +108,7 @@ finally mark it as `\Seen`.
EOF
<!-- -->
$ env -i PATH="/usr/bin:/bin" USER="testuser" \
- doveadm -c "$BASEDIR/dovecot.conf" flags add "\\Seen" mailbox "foo" "*"
+ doveadm -c "$BASEDIR/dovecot.conf" flags add --no-userdb-lookup "\\Seen" mailbox "foo" "*"
Normally [`dovecot-lda`(1)][Dovecot LDA] tries to do a userdb lookup in
order to determine the user's home directory. Since we didn't configure
@@ -136,7 +142,7 @@ and `remote` accounts.
Run [`interimap`(1)] without `--watch` in order to create the database.
- $ env -i PATH="$PATH" perl -I./lib -T ./interimap --config="$BASEDIR/interimap.conf"
+ $ env -i PATH="$PATH" perl -T ./interimap --config="$BASEDIR/interimap.conf"
Creating new schema in database file …/interimap.db
database: Created mailbox INBOX
[…]
@@ -144,8 +150,8 @@ Run [`interimap`(1)] without `--watch` in order to create the database.
You can now run [`interimap`(1)] with `--watch` set, here to one second
to observe synchronization steps early.
- $ env -i PATH="$PATH" perl -I./lib -T ./interimap --config="$BASEDIR/interimap.conf" \
- --watch=1 --debug
+ $ env -i PATH="$PATH" perl -T ./interimap --config="$BASEDIR/interimap.conf" \
+ --watch=1 --debug
Use instructions from the [previous section][Mail storage access]
(substituting `testuser` with `local` or `remote`) in order to simulate
@@ -168,13 +174,13 @@ Create a [`pullimap`(1)] configuration file with as section `[foo]`.
Run [`pullimap`(1)] without `--idle` in order to create the state file.
- $ env -i PATH="$PATH" perl -I./lib -T ./pullimap --config="$BASEDIR/pullimap.conf" \
- --no-delivery foo
+ $ env -i PATH="$PATH" perl -T ./pullimap --config="$BASEDIR/pullimap.conf" \
+ --no-delivery foo
You can now run [`pullimap`(1)] with `--idle` set.
- $ env -i PATH="$PATH" perl -I./lib -T ./pullimap --config="$BASEDIR/pullimap.conf" \
- --no-delivery --idle --debug foo
+ $ env -i PATH="$PATH" perl -T ./pullimap --config="$BASEDIR/pullimap.conf" \
+ --no-delivery --idle --debug foo
Use instructions from the [previous section][Mail storage access]
in order to simulate activity on the “remote” server (in the relevant
@@ -193,14 +199,13 @@ recursively remove the directory `$BASEDIR`.
[IMAP4rev1]: https://tools.ietf.org/html/rfc3501
[Dovecot]: https://dovecot.org
-[Dovecot Logging]: https://doc.dovecot.org/admin_manual/dovecot_logging/
-[Dovecot LDA]: https://wiki.dovecot.org/LDA
-[`getlogin`(3)]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getlogin.html
-[User Databases]: https://doc.dovecot.org/configuration_manual/authentication/user_databases_userdb/
-[Maildir]: https://wiki.dovecot.org/MailLocation/Maildir
+[Dovecot Logging]: https://doc.dovecot.org/latest/core/admin/logging.html#dovecot-logging
+[Dovecot LDA]: https://doc.dovecot.org/latest/core/config/delivery/lda.html
+[User Databases]: https://doc.dovecot.org/latest/core/config/auth/userdb.html
+[Maildir]: https://doc.dovecot.org/latest/core/config/mailbox_formats/maildir.html
[RFC 2342]: https://tools.ietf.org/html/rfc2342
-[Dovecot Namespaces]: https://doc.dovecot.org/configuration_manual/namespace/
+[Dovecot Namespaces]: https://doc.dovecot.org/latest/core/config/namespaces.html
[`interimap`(1)]: interimap.1.html
[`pullimap`(1)]: pullimap.1.html
-[`doveadm`(1)]: https://wiki.dovecot.org/Tools/Doveadm
-[`%`-variable]: https://doc.dovecot.org/configuration_manual/config_file/config_variables/
+[`doveadm`(1)]: https://doc.dovecot.org/latest/core/man/doveadm.1.html
+[settings variables]: https://doc.dovecot.org/latest/core/settings/variables.html
diff --git a/doc/getting-started.md b/doc/getting-started.md
index c497ec6..e89eb19 100644
--- a/doc/getting-started.md
+++ b/doc/getting-started.md
@@ -20,7 +20,7 @@ format][mbox]). Local mail clients usually access it directly. They
also often maintain their own cache in order to speed up message header
listing and searches.
-While most bidirectional synchronisation software (such as [OfflineIMAP])
+While most bidirectional synchronization software (such as [OfflineIMAP])
are able to handle a mail storage in Maildir format, *InterIMAP is
not*. Instead, InterIMAP needs an [IMAP4rev1] server on *both* peers
to synchronize. This may sound like a severe limitation at first, but by
@@ -30,7 +30,8 @@ perform significant optimizations, yielding [much faster](benchmark.html)
synchronization.
*Note*: InterIMAP uses the [Quick Mailbox Resynchronization][RFC 7162]
extension for stateful synchronization, hence won't work on IMAP servers
-that don't advertise support for that extension.
+that don't advertise [support](https://www.imapwiki.org/Specs#line-68)
+for that extension.
Installing an [IMAP4rev1] server on a single-user workstation may sound
overkill, but we argue that most systems, not only servers, come with a
@@ -83,7 +84,7 @@ mailbox delivery) then don't disable them, and modify Dovecot's system
wide configuration instead. Same thing if your mail client isn't able
to spawn a command for IMAP communication, and instead insists on
connecting to a network socket (in that case you'll even need to
-configure [user authentication](https://wiki.dovecot.org/Authentication)
+configure [user authentication](https://doc.dovecot.org/latest/core/config/auth/overview.html)
for the IMAP service, which is out of scope for the present document).
Run the following command to terminate and disable the system-wide
@@ -93,14 +94,19 @@ Dovecot processes.
Create a new directory `$XDG_CONFIG_HOME/dovecot` holding the local
-Dovecot configuration:
+Dovecot 2.4 configuration:
$ install -m0700 -vd ${XDG_CONFIG_HOME:-~/.config}/dovecot
<!-- -->
$ cat >${XDG_CONFIG_HOME:-~/.config}/dovecot/dovecot.conf <<-EOF
+ dovecot_config_version = 2.4.0
+ dovecot_storage_version = 2.4.0
+
ssl = no
- mail_location = maildir:~/Mail
- namespace {
+ mail_home = /home/%{user | username | lower}
+ mail_driver = maildir
+ mail_path = ~/Mail
+ namespace inbox {
inbox = yes
separator = /
}
@@ -112,9 +118,10 @@ Some remarks on the above:
running `` `doveconf -nc ${XDG_CONFIG_HOME:-~/.config}/dovecot/dovecot.conf` ``.
* Messages will be stored in Maildir format under `~/Mail`. Ensure
the directory is either *empty* or *doesn't exist* before
- continuing! You may want to choose a different [format](https://wiki.dovecot.org/MailboxFormat)
- here, or simply append `:LAYOUT=fs` to the `mail_location` value in
- order to use a nicer (File System like) Maildir layout.
+ continuing! You may want to choose a different
+ [format](https://doc.dovecot.org/latest/core/config/mailbox_formats/overview.html)
+ here, or simply set `mailbox_list_layout = fs` in order to use a
+ nicer (File System like) Maildir layout.
* The `separator` setting defines the IMAP hierarchy delimiter. This
is orthogonal to the Maildir layout delimiter, and you can safely
change it later (even on an existing mail store). Popular hierarchy
@@ -197,7 +204,7 @@ for the sake of clarity we start from an empty file here.
shell process doesn't linger around during the IMAP session.)
3. And finally append a `[remote]` section with your account
- information at `imap.example.org` (adapt the values accordingly):
+ information at `imap.example.net` (adapt the values accordingly):
$ cat >>${XDG_CONFIG_HOME:-~/.config}/interimap/config <<-EOF
@@ -280,7 +287,7 @@ Manual
[IMAP4rev1]: https://tools.ietf.org/html/rfc3501
[INI file]: https://en.wikipedia.org/wiki/INI_file
[`interimap`(1)]: interimap.1.html
-[LMTP server]: https://doc.dovecot.org/configuration_manual/protocols/lmtp_server/
+[LMTP server]: https://doc.dovecot.org/latest/core/config/delivery/lmtp.html
[Maildir]: https://en.wikipedia.org/wiki/Maildir
[mbox]: https://en.wikipedia.org/wiki/Mbox
[MTA]: https://en.wikipedia.org/wiki/Message_transfer_agent
diff --git a/doc/index.md b/doc/index.md
index d475592..4dd72f1 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -1,18 +1,32 @@
% InterIMAP & PullIMAP
% [Guilhem Moulin](mailto:guilhem@fripost.org)
+InterIMAP synchronizes emails and their metadata between a remote IMAP
+server and local storage. By leveraging the [*Quick Mailbox
+Resynchronization*][RFC 7162] IMAP extension, it can offer [*much better
+performance*](benchmark.html) than tools such as [OfflineIMAP].
+
+PullIMAP retrieves messages from an IMAP mailbox and deliver them
+locally. It can use the the [*IDLE*][RFC 2177] IMAP extension to reduce
+both latency and network traffic.
+
+[RFC 2177]: https://tools.ietf.org/html/rfc2177
+[RFC 7162]: https://tools.ietf.org/html/rfc7162
+[OfflineIMAP]: https://www.offlineimap.org/
+
General documentation
---------------------
* [Getting started with InterIMAP](getting-started.html)
* [Multi-remote setup for InterIMAP](multi-account.html)
* [InterIMAP benchmark metrics and comparison](benchmark.html)
+ * [Presentation at DebConf19](https://debconf19.debconf.org/talks/78-interimap-the-case-for-local-imap-servers-and-fast-bidirectional-synchronization/)
Manuals (HTML versions)
-----------------------
* [`interimap`(1)](interimap.1.html) — Fast bidirectional
- synchronization for QRESYNC-capable IMAP servers
+ synchronization for IMAP servers
* [`pullimap`(1)](pullimap.1.html) — Pull mails from an IMAP mailbox
and deliver them to an SMTP session
diff --git a/doc/interimap.1.md b/doc/interimap.1.md
index f10ced6..18b3581 100644
--- a/doc/interimap.1.md
+++ b/doc/interimap.1.md
@@ -249,7 +249,7 @@ Valid options are:
Two wildcards are available, and passed verbatim to the IMAP server:
a ‘\*’ character matches zero or more characters, while a ‘%’
character matches zero or more characters up to the hierarchy
- delimiter. Hardcoding the hierarchy delimiter in this setting is
+ delimiter. Hard-coding the hierarchy delimiter in this setting is
not advised because the server might silently change it at some
point. A null character should be used instead. For instance, if
*list-mailbox* is set `"foo\x00bar"` then, assuming the hierarchy
@@ -309,7 +309,7 @@ Valid options are:
`type=imap` and `type=imaps` are respectively used for IMAP and IMAP
over SSL/TLS connections over an INET socket.
`type=tunnel` causes `interimap` to create an unnamed pair of
- connected sockets for interprocess communication with a *command*
+ connected sockets for inter-process communication with a *command*
instead of opening a network socket.
Note that specifying `type=tunnel` in the `[remote]` section makes
the default *database* to be `localhost.db`.
@@ -317,7 +317,9 @@ Valid options are:
*host*
-: Server hostname, for `type=imap` and `type=imaps`.
+: Server hostname or IP address, for `type=imap` and `type=imaps`.
+ The value can optionally be enclosed in square brackets to force its
+ interpretation as an IP literal (hence skip name resolution).
(Default: `localhost`.)
*port*
@@ -327,8 +329,8 @@ Valid options are:
*proxy*
-: An optional SOCKS proxy to use for TCP connections to the IMAP
- server (`type=imap` and `type=imaps` only), formatted as
+: Optional SOCKS proxy to use for TCP connections to the IMAP server
+ (`type=imap` and `type=imaps` only), formatted as
`PROTOCOL://[USER:PASSWORD@]PROXYHOST[:PROXYPORT]`.
If `PROXYPORT` is omitted, it is assumed at port 1080.
Only [SOCKSv5][RFC 1928] is supported (with optional
@@ -376,58 +378,95 @@ Valid options are:
*null-stderr*
: Whether to redirect *command*'s standard error to `/dev/null` for
- `type=tunnel`. (Default: `NO`.)
+ `type=tunnel`. This option is ignored when the `--debug` flag is
+ set. (Default: `NO`.)
*SSL_protocols*
-: A space-separated list of SSL protocols to enable or disable (if
- prefixed with an exclamation mark `!`. Known protocols are `SSLv2`,
- `SSLv3`, `TLSv1`, `TLSv1.1`, `TLSv1.2`, and `TLSv1.3`. Enabling a
- protocol is a short-hand for disabling all other protocols.
- (Default: `!SSLv2 !SSLv3 !TLSv1 !TLSv1.1`, i.e., only enable TLSv1.2
- and above.)
+: Space-separated list of SSL/TLS protocol versions to explicitly
+ enable (or disable if prefixed with an exclamation mark `!`).
+ Potentially known protocols are `SSLv2`, `SSLv3`, `TLSv1`,
+ `TLSv1.1`, `TLSv1.2`, and `TLSv1.3`, depending on the OpenSSL
+ version used.
+ Enabling a protocol is a short-hand for disabling all other
+ protocols.
-*SSL_cipher_list*
+ *DEPRECATED*: Use *SSL_protocol_min* and/or *SSL_protocol_max*
+ instead.
-: The cipher list to send to the server. Although the server
- determines which cipher suite is used, it should take the first
- supported cipher in the list sent by the client. See
- [`ciphers`(1ssl)] for more information.
+*SSL_protocol_min*, *SSL_protocol_max*
+
+: Set minimum resp. maximum SSL/TLS protocol version to use for the
+ connection. Potentially recognized values are `SSLv3`, `TLSv1`,
+ `TLSv1.1`, `TLSv1.2`, and `TLSv1.3`, depending on the OpenSSL
+ version used.
+
+*SSL_cipherlist*, *SSL_ciphersuites*
+
+: Sets the TLSv1.2 and below cipher list resp. TLSv1.3 cipher suites.
+ The combination of these lists is sent to the server, which then
+ determines which cipher to use (normally the first supported one
+ from the list sent by the client). The default suites depend on the
+ OpenSSL version and its configuration, see [`ciphers`(1ssl)] for
+ more information.
*SSL_fingerprint*
-: Fingerprint of the server certificate's Subject Public Key Info, in
- the form `[ALGO$]DIGEST_HEX` where `ALGO` is the used algorithm (by
- default `sha256`).
+: Space-separated list of acceptable fingerprints for the server
+ certificate's Subject Public Key Info, in the form
+ `[ALGO$]DIGEST_HEX` where `ALGO` is the digest algorithm (by default
+ `sha256`).
Attempting to connect to a server with a non-matching certificate
SPKI fingerprint causes `interimap` to abort the connection during
the SSL/TLS handshake.
The following command can be used to compute the SHA-256 digest of a
certificate's Subject Public Key Info:
- openssl x509 -in /path/to/server/certificate.pem -pubkey \
- | openssl pkey -pubin -outform DER \
- | openssl dgst -sha256
+ $ openssl x509 -in /path/to/server/certificate.pem -pubkey \
+ | openssl pkey -pubin -outform DER \
+ | openssl dgst -sha256
+
+ Specifying multiple digest values can be useful in key rollover
+ scenarios and/or when the server supports certificates of different
+ types (for instance a dual-cert RSA/ECDSA setup). In that case the
+ connection is aborted when none of the specified digests matches.
*SSL_verify*
-: Whether to verify the server certificate chain.
- Note that using *SSL_fingerprint* to specify the fingerprint of the
- server certificate is an orthogonal authentication measure as it
- ignores the CA chain.
+: Whether to 1/ verify the server certificate chain; and 2/ match its
+ Subject Alternative Name (SAN) or Subject CommonName (CN) against
+ the value of the *host* option.
(Default: `YES`.)
+ Note that using *SSL_fingerprint* to specify the fingerprint of the
+ server certificate provides an independent server authentication
+ measure as it pins directly its key material and ignore its chain of
+ trust.
+
+*SSL_CAfile*
+
+: File containing trusted certificates to use during server
+ certificate verification when `SSL_verify=YES`.
+
+ Trusted CA certificates are loaded from the default system locations
+ unless one (or both) of *SSL_CAfile* or *SSL_CApath* is set.
+
*SSL_CApath*
-: Directory to use for server certificate verification if
+: Directory to use for server certificate verification when
`SSL_verify=YES`.
This directory must be in “hash format”, see [`verify`(1ssl)] for
more information.
-*SSL_CAfile*
+ Trusted CA certificates are loaded from the default system locations
+ unless one (or both) of *SSL_CAfile* or *SSL_CApath* is set.
-: File containing trusted certificates to use during server
- certificate authentication if `SSL_verify=YES`.
+*SSL_hostname*
+
+: Name to use for the TLS SNI (Server Name Indication) extension. The
+ default value is taken from the *host* option when it is a hostname,
+ and to the empty string when it is an IP literal.
+ Setting *SSL_hostname* to the empty string explicitly disables SNI.
Supported extensions {#supported-extensions}
====================
@@ -469,7 +508,7 @@ Known bugs and limitations
* Because the [IMAP protocol][RFC 3501] doesn't provide a way for
clients to determine whether a disappeared mailbox was deleted or
renamed, `interimap` aborts when a known mailbox disappeared from one
- server but not the other. The `--delete` (resp. `rename`) command
+ server but not the other. The `--delete` (resp. `--rename`) command
should be used instead to delete (resp. rename) the mailbox on both
servers as well as within `interimap`'s internal database.
@@ -561,6 +600,6 @@ A _getting started_ guide is available [there](getting-started.html).
[INI file]: https://en.wikipedia.org/wiki/INI_file
[PCRE]: https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions
-[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/ciphers.html
-[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/verify.html
-[`doveadm-deduplicate`(1)]: https://wiki.dovecot.org/Tools/Doveadm/Deduplicate
+[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html
+[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-verify.html
+[`doveadm-deduplicate`(1)]: https://doc.dovecot.org/latest/core/man/doveadm-deduplicate.1.html
diff --git a/doc/multi-account.md b/doc/multi-account.md
index cc0a1b8..58fdd2d 100644
--- a/doc/multi-account.md
+++ b/doc/multi-account.md
@@ -82,20 +82,23 @@ over: they will only live on the local instance.
$ cat >${XDG_CONFIG_HOME:-~/.config}/dovecot/dovecot.conf <<-EOF
ssl = no
- namespace {
- location = maildir:~/Mail
- inbox = yes
- separator = /
+ namespace indbox {
+ mail_driver = maildir
+ mail_path = ~/Mail
+ inbox = yes
+ separator = /
}
namespace perso {
- prefix = perso/
- location = maildir:~/Mail/perso
- separator = /
+ prefix = perso/
+ mail_driver = maildir
+ mail_path = ~/Mail/perso
+ separator = /
}
namespace work {
- prefix = work/
- location = maildir:~/Mail/work
- separator = /
+ prefix = work/
+ mail_driver = maildir
+ mail_path = ~/Mail/work
+ separator = /
}
EOF
@@ -205,5 +208,5 @@ Template user unit for systemd are provided in order to run these
[Getting Started]: getting-started.html
[RFC 2342]: https://tools.ietf.org/html/rfc2342
[RFC 3501]: https://tools.ietf.org/html/rfc3501
-[Dovecot namespaces]: https://doc.dovecot.org/configuration_manual/namespace/
+[Dovecot namespaces]: https://doc.dovecot.org/latest/core/config/namespaces.html
[`interimap`(1)]: interimap.1.html
diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md
index 5028a14..89969b2 100644
--- a/doc/pullimap.1.md
+++ b/doc/pullimap.1.md
@@ -106,14 +106,14 @@ Valid options are:
*deliver-ehlo*
-: Hostname to use in `EHLO` or `LHLO` commands.
+: Name to use in `EHLO` or `LHLO` commands.
(Default: `localhost.localdomain`.)
*deliver-rcpt*
: Message recipient. Note that the local part needs to quoted if it
contains special characters; see [RFC 5321] for details.
- (Default: the username associated with the effective uid of the
+ (Default: the username associated with the effective user ID of the
`pullimap` process.)
*purge-after*
@@ -123,7 +123,7 @@ Valid options are:
`SEARCH` criterion ignoring time and timezone.)
If *purge-after* is set to `0` then messages are deleted immediately
after delivery. Otherwise `pullimap` issues an IMAP `SEARCH` (or
- extended `SEARCH` on servers advertizing the [`ESEARCH`][RFC 4731]
+ extended `SEARCH` on servers advertising the [`ESEARCH`][RFC 4731]
capability) command to list old messages; if `--idle` is set then
the `SEARCH` command is issued again every 12 hours.
@@ -133,13 +133,15 @@ Valid options are:
`type=imap` and `type=imaps` are respectively used for IMAP and IMAP
over SSL/TLS connections over an INET socket.
`type=tunnel` causes `pullimap` to create an unnamed pair of
- connected sockets for interprocess communication with a *command*
+ connected sockets for inter-process communication with a *command*
instead of opening a network socket.
(Default: `imaps`.)
*host*
-: Server hostname, for `type=imap` and `type=imaps`.
+: Server hostname or IP address, for `type=imap` and `type=imaps`.
+ The value can optionally be enclosed in square brackets to force its
+ interpretation as an IP literal (hence skip name resolution).
(Default: `localhost`.)
*port*
@@ -149,8 +151,8 @@ Valid options are:
*proxy*
-: An optional SOCKS proxy to use for TCP connections to the IMAP
- server (`type=imap` and `type=imaps` only), formatted as
+: Optional SOCKS proxy to use for TCP connections to the IMAP server
+ (`type=imap` and `type=imaps` only), formatted as
`PROTOCOL://[USER:PASSWORD@]PROXYHOST[:PROXYPORT]`.
If `PROXYPORT` is omitted, it is assumed at port 1080.
Only [SOCKSv5][RFC 1928] is supported (with optional
@@ -200,54 +202,90 @@ Valid options are:
*SSL_protocols*
-: A space-separated list of SSL protocols to enable or disable (if
- prefixed with an exclamation mark `!`. Known protocols are `SSLv2`,
- `SSLv3`, `TLSv1`, `TLSv1.1`, `TLSv1.2`, and `TLSv1.3`. Enabling a
- protocol is a short-hand for disabling all other protocols.
- (Default: `!SSLv2 !SSLv3 !TLSv1 !TLSv1.1`, i.e., only enable TLSv1.2
- and above.)
+: Space-separated list of SSL/TLS protocol versions to explicitly
+ enable (or disable if prefixed with an exclamation mark `!`).
+ Potentially known protocols are `SSLv2`, `SSLv3`, `TLSv1`,
+ `TLSv1.1`, `TLSv1.2`, and `TLSv1.3`, depending on the OpenSSL
+ version used.
+ Enabling a protocol is a short-hand for disabling all other
+ protocols.
-*SSL_cipher_list*
+ *DEPRECATED*: Use *SSL_protocol_min* and/or *SSL_protocol_max*
+ instead.
-: The cipher list to send to the server. Although the server
- determines which cipher suite is used, it should take the first
- supported cipher in the list sent by the client. See
- [`ciphers`(1ssl)] for more information.
+*SSL_protocol_min*, *SSL_protocol_max*
+
+: Set minimum resp. maximum SSL/TLS protocol version to use for the
+ connection. Potentially recognized values are `SSLv3`, `TLSv1`,
+ `TLSv1.1`, `TLSv1.2`, and `TLSv1.3`, depending on the OpenSSL
+ version used.
+
+*SSL_cipherlist*, *SSL_ciphersuites*
+
+: Sets the TLSv1.2 and below cipher list resp. TLSv1.3 cipher suites.
+ The combination of these lists is sent to the server, which then
+ determines which cipher to use (normally the first supported one
+ from the list sent by the client). The default suites depend on the
+ OpenSSL version and its configuration, see [`ciphers`(1ssl)] for
+ more information.
*SSL_fingerprint*
-: Fingerprint of the server certificate's Subject Public Key Info, in
- the form `[ALGO$]DIGEST_HEX` where `ALGO` is the used algorithm (by
- default `sha256`).
+: Space-separated list of acceptable fingerprints for the server
+ certificate's Subject Public Key Info, in the form
+ `[ALGO$]DIGEST_HEX` where `ALGO` is the digest algorithm (by default
+ `sha256`).
Attempting to connect to a server with a non-matching certificate
SPKI fingerprint causes `pullimap` to abort the connection during
the SSL/TLS handshake.
The following command can be used to compute the SHA-256 digest of a
certificate's Subject Public Key Info:
- openssl x509 -in /path/to/server/certificate.pem -pubkey \
- | openssl pkey -pubin -outform DER \
- | openssl dgst -sha256
+ $ openssl x509 -in /path/to/server/certificate.pem -pubkey \
+ | openssl pkey -pubin -outform DER \
+ | openssl dgst -sha256
+
+ Specifying multiple digest values can be useful in key rollover
+ scenarios and/or when the server supports certificates of different
+ types (for instance a dual-cert RSA/ECDSA setup). In that case the
+ connection is aborted when none of the specified digests matches.
*SSL_verify*
-: Whether to verify the server certificate chain.
- Note that using *SSL_fingerprint* to specify the fingerprint of the
- server certificate is an orthogonal authentication measure as it
- ignores the CA chain.
+: Whether to 1/ verify the server certificate chain; and 2/ match its
+ Subject Alternative Name (SAN) or Subject CommonName (CN) against
+ the value of the *host* option.
(Default: `YES`.)
+ Note that using *SSL_fingerprint* to specify the fingerprint of the
+ server certificate provides an independent server authentication
+ measure as it pins directly its key material and ignore its chain of
+ trust.
+
+*SSL_CAfile*
+
+: File containing trusted certificates to use during server
+ certificate verification when `SSL_verify=YES`.
+
+ Trusted CA certificates are loaded from the default system locations
+ unless one (or both) of *SSL_CAfile* or *SSL_CApath* is set.
+
*SSL_CApath*
-: Directory to use for server certificate verification if
+: Directory to use for server certificate verification when
`SSL_verify=YES`.
This directory must be in “hash format”, see [`verify`(1ssl)] for
more information.
-*SSL_CAfile*
+ Trusted CA certificates are loaded from the default system locations
+ unless one (or both) of *SSL_CAfile* or *SSL_CApath* is set.
-: File containing trusted certificates to use during server
- certificate authentication if `SSL_verify=YES`.
+*SSL_hostname*
+
+: Name to use for the TLS SNI (Server Name Indication) extension. The
+ default value is taken from the *host* option when it is a hostname,
+ and to the empty string when it is an IP literal.
+ Setting *SSL_hostname* to the empty string explicitly disables SNI.
Control flow {#control-flow}
============
@@ -303,7 +341,7 @@ In more details, `pullimap` works as follows:
to terminate it gracefully.
3. Issue a `UID STORE` command to mark all retrieved messages (and
- stalled UIDs found in the *statefile* after the eigth byte) as
+ stalled UIDs found in the *statefile* after the eighth byte) as
`\Seen`.
4. Update the *statefile* with the new UIDNEXT value (bytes 5-8).
@@ -369,8 +407,8 @@ Standards
[RFC 4731]: https://tools.ietf.org/html/rfc4731
[INI file]: https://en.wikipedia.org/wiki/INI_file
-[`fetchmail`(1)]: http://www.fetchmail.info/
+[`fetchmail`(1)]: https://www.fetchmail.info/
[`getmail`(1)]: http://pyropus.ca/software/getmail/
-[`write`(2)]: http://man7.org/linux/man-pages/man2/write.2.html
-[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/ciphers.html
-[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/verify.html
+[`write`(2)]: https://man7.org/linux/man-pages/man2/write.2.html
+[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html
+[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-verify.html
diff --git a/doc/template.html b/doc/template.html
index 41bf3d7..c3809ad 100644
--- a/doc/template.html
+++ b/doc/template.html
@@ -14,15 +14,23 @@ $if(keywords)$
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
$endif$
<title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title>
- <style type="text/css">
+ <style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
- pre{tab-size: 4; -moz-tab-size: 4;}
table{width: 100%; margin-bottom: 3ex;}
table > thead > tr.header > th{border-bottom: 2px solid #ddd; padding: 8px;}
table > tbody > tr > td{border-bottom: 1px solid #ddd; padding: 6px;}
+ pre {
+ padding: 16px;
+ font-size: 85%;
+ line-height: 1.45;
+ background-color: #f6f8fa;
+ border-radius: 6px;
+ tab-size: 4;
+ -moz-tab-size: 4;
+ }
@media only screen and (min-width: 600px) {
.parent {
float: right;
@@ -64,7 +72,7 @@ $endfor$
$if(title)$
<div class="page-header">
$if(parent)$
- <div class=parent><a href="$parent$"><span class="glyphicon glyphicon-circle-arrow-up" aria-hidden="true"></span> Parent</a></div>
+ <div class=parent><a href="$parent$"><span class="fa fa-arrow-circle-up" aria-hidden="true"></span> Parent</a></div>
$endif$
<h1 style="">$title$</h1>
</div>
diff --git a/interimap b/interimap
index f9be72d..b4302f9 100755
--- a/interimap
+++ b/interimap
@@ -2,7 +2,7 @@
#----------------------------------------------------------------------
# Fast bidirectional synchronization for QRESYNC-capable IMAP servers
-# Copyright © 2015-2019 Guilhem Moulin <guilhem@fripost.org>
+# Copyright © 2015-2022 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,25 +15,25 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
#----------------------------------------------------------------------
use v5.14.2;
use strict;
use warnings;
-our $VERSION = '0.5';
+our $VERSION = '0.5.8';
my $NAME = 'interimap';
my $DATABASE_VERSION = 1;
use Getopt::Long qw/:config posix_default no_ignore_case gnu_compat
bundling auto_version/;
use DBI ':sql_types';
use DBD::SQLite::Constants ':file_open';
-use Fcntl qw/F_GETFD F_SETFD FD_CLOEXEC/;
+use Fcntl qw/O_WRONLY O_CREAT O_EXCL F_GETFD F_SETFD FD_CLOEXEC/;
use List::Util 'first';
-use lib 'lib';
-use Net::IMAP::InterIMAP 0.0.5 qw/xdg_basedir read_config compact_set/;
+use lib "./lib";
+use Net::IMAP::InterIMAP 0.5.8 qw/xdg_basedir read_config compact_set/;
# Clean up PATH
$ENV{PATH} = join ':', qw{/usr/bin /bin};
@@ -160,6 +160,12 @@ $SIG{TERM} = sub { cleanup(); exit 0; };
# Open (and maybe create) the database
{
+ # don't auto-create in long-lived mode
+ unless ($CONFIG{watch} or -e $DBFILE) {
+ sysopen(my $fh, $DBFILE, O_WRONLY | O_CREAT | O_EXCL, 0600) or die "Can't create $DBFILE: $!";
+ close $fh or warn "close: $!";
+ }
+
my $dbi_data_source = "dbi:SQLite:dbname=".$DBFILE;
my %dbi_attrs = (
AutoCommit => 0,
@@ -167,8 +173,6 @@ $SIG{TERM} = sub { cleanup(); exit 0; };
sqlite_use_immediate_transaction => 1,
sqlite_open_flags => SQLITE_OPEN_READWRITE
);
- # don't auto-create in long-lived mode
- $dbi_attrs{sqlite_open_flags} |= SQLITE_OPEN_CREATE unless defined $CONFIG{watch};
$DBH = DBI::->connect($dbi_data_source, undef, undef, \%dbi_attrs);
$DBH->sqlite_busy_timeout(250);
@@ -287,7 +291,7 @@ sub list_mailboxes($) {
my ($m) = sort keys %$delims;
$imap->{delimiter} = delete $delims->{$m};
} else {
- # didn't get a non-INBOX LIST reply so we need to explicitely query
+ # didn't get a non-INBOX LIST reply so we need to explicitly query
# the hierarchy delimiter
get_delimiter($name, $imap, $ref);
}
diff --git a/interimap.sample b/interimap.sample
index 2a7b8de..36eeb2a 100644
--- a/interimap.sample
+++ b/interimap.sample
@@ -1,4 +1,4 @@
-#database = imap.example.org.db
+#database = imap.example.net.db
# only consider subscribed mailboxes
list-select-opts = SUBSCRIBED
@@ -15,7 +15,7 @@ null-stderr = YES
[remote]
#type = imaps
-host = imap.example.org
+host = imap.example.net
#port = 993
#proxy = socks5h://localhost:9050
username = guilhem
@@ -23,10 +23,8 @@ password = xxxxxxxxxxxxxxxx
#compress = YES
# SSL options
-SSL_CApath = /etc/ssl/certs
#SSL_verify = YES
-#SSL_protocols = !SSLv2 !SSLv3 !TLSv1 !TLSv1.1
-#SSL_cipherlist = EECDH+AESGCM:!MEDIUM:!LOW:!EXP:!aNULL:!eNULL
+#SSL_protocol_min = TLSv1.2
#SSL_fingerprint = sha256$29111aea5d5be7e448bdc5c6e8a9d03bc9221c53c09b1cfbe6f953221e24dda0
# vim:ft=dosini
diff --git a/interimap.service b/interimap.service
index 6d7fa45..6abc874 100644
--- a/interimap.service
+++ b/interimap.service
@@ -1,11 +1,12 @@
[Unit]
Description=Fast bidirectional synchronization for QRESYNC-capable IMAP servers
Documentation=man:interimap(1)
+Documentation=https://guilhem.org/interimap/interimap.1.html
Wants=network-online.target
After=network-online.target
[Service]
-ExecStart=/usr/bin/interimap --watch=60
+ExecStart=@bindir@/interimap --watch=60
RestartSec=10min
Restart=on-failure
diff --git a/interimap@.service b/interimap@.service
index 6957b79..f870065 100644
--- a/interimap@.service
+++ b/interimap@.service
@@ -1,12 +1,13 @@
[Unit]
Description=Fast bidirectional synchronization for QRESYNC-capable IMAP servers (instance %i)
Documentation=man:interimap(1)
+Documentation=https://guilhem.org/interimap/interimap.1.html
PartOf=interimap.service
Wants=network-online.target
After=network-online.target
[Service]
-ExecStart=/usr/bin/interimap --config=%i --watch=60
+ExecStart=@bindir@/interimap --config=%i --watch=60
RestartSec=10min
Restart=on-failure
diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm
index 1bff06e..10162c7 100644
--- a/lib/Net/IMAP/InterIMAP.pm
+++ b/lib/Net/IMAP/InterIMAP.pm
@@ -1,6 +1,6 @@
#----------------------------------------------------------------------
# A minimal IMAP4 client for QRESYNC-capable servers
-# Copyright © 2015-2019 Guilhem Moulin <guilhem@fripost.org>
+# Copyright © 2015-2022 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,10 +13,10 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
#----------------------------------------------------------------------
-package Net::IMAP::InterIMAP v0.0.5;
+package Net::IMAP::InterIMAP v0.5.8;
use v5.20.0;
use warnings;
use strict;
@@ -24,7 +24,7 @@ use strict;
use Compress::Raw::Zlib qw/Z_OK Z_STREAM_END Z_FULL_FLUSH Z_SYNC_FLUSH MAX_WBITS/;
use Config::Tiny ();
use Errno qw/EEXIST EINTR/;
-use Net::SSLeay 1.73 ();
+use Net::SSLeay 1.86_06 ();
use List::Util qw/all first/;
use POSIX ':signal_h';
use Socket qw/SOCK_STREAM SOCK_RAW SOCK_CLOEXEC IPPROTO_TCP SHUT_RDWR
@@ -62,9 +62,13 @@ my %OPTIONS = (
command => qr/\A(\P{Control}+)\z/,
'null-stderr' => qr/\A(YES|NO)\z/i,
compress => qr/\A(YES|NO)\z/i,
- SSL_protocols => qr/\A(!?$RE_SSL_PROTO(?: !?$RE_SSL_PROTO)*)\z/,
- SSL_fingerprint => qr/\A((?:[A-Za-z0-9]+\$)?\p{AHex}+)\z/,
+ SSL_protocols => qr/\A(!?$RE_SSL_PROTO(?: !?$RE_SSL_PROTO)*)\z/, # TODO deprecated, remove in 0.6
+ SSL_protocol_min => qr/\A(\P{Control}+)\z/,
+ SSL_protocol_max => qr/\A(\P{Control}+)\z/,
+ SSL_fingerprint => qr/\A((?:[A-Za-z0-9]+\$)?\p{AHex}+(?: (?:[A-Za-z0-9]+\$)?\p{AHex}+)*)\z/,
SSL_cipherlist => qr/\A(\P{Control}+)\z/,
+ SSL_ciphersuites => qr/\A(\P{Control}*)\z/, # "an empty list is permissible"
+ SSL_hostname => qr/\A(\P{Control}*)\z/,
SSL_verify => qr/\A(YES|NO)\z/i,
SSL_CApath => qr/\A(\P{Control}+)\z/,
SSL_CAfile => qr/\A(\P{Control}+)\z/,
@@ -324,19 +328,19 @@ sub new($%) {
my $pid = fork // $self->panic("fork: $!");
unless ($pid) {
# children
- close($self->{S}) or $self->panic("Can't close: $!");
- open STDIN, '<&', $s or $self->panic("Can't dup: $!");
- open STDOUT, '>&', $s or $self->panic("Can't dup: $!");
+ close($self->{S}) or $self->panic("close: $!");
+ open STDIN, '<&', $s or $self->panic("dup: $!");
+ open STDOUT, '>&', $s or $self->panic("dup: $!");
my $stderr2;
- if ($self->{'null-stderr'} // 0) {
+ if (($self->{'null-stderr'} // 0) and !($self->{debug} // 0)) {
open $stderr2, '>&', *STDERR;
- open STDERR, '>', '/dev/null' or $self->panic("Can't open /dev/null: $!");
+ open STDERR, '>', '/dev/null' or $self->panic("open(/dev/null): $!");
}
my $sigset = POSIX::SigSet::->new(SIGINT);
my $oldsigset = POSIX::SigSet::->new();
- sigprocmask(SIG_BLOCK, $sigset, $oldsigset) // $self->panic("Can't block SIGINT: $!");
+ sigprocmask(SIG_BLOCK, $sigset, $oldsigset) // $self->panic("sigprocmask: $!");
unless (exec $command) {
my $err = $!;
@@ -344,12 +348,12 @@ sub new($%) {
close STDERR;
open STDERR, '>&', $stderr2;
}
- $self->panic("Can't exec: $err");
+ $self->panic("exec: $err");
}
}
# parent
- close($s) or $self->panic("Can't close: $!");
+ close($s) or $self->panic("close: $!");
}
else {
foreach (qw/host port/) {
@@ -359,9 +363,9 @@ sub new($%) {
: $self->_tcp_connect(@$self{qw/host port/});
if (defined $self->{keepalive}) {
setsockopt($self->{S}, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
- or $self->fail("Can't setsockopt SO_KEEPALIVE: $!");
+ or $self->fail("setsockopt SO_KEEPALIVE: $!");
setsockopt($self->{S}, Socket::IPPROTO_TCP, Socket::TCP_KEEPIDLE, 60)
- or $self->fail("Can't setsockopt TCP_KEEPIDLE: $!");
+ or $self->fail("setsockopt TCP_KEEPIDLE: $!");
}
}
@@ -464,6 +468,7 @@ sub new($%) {
$self->logger('S: xxx ', $IMAP_text);
$self->{debug} = $dbg;
}
+ $self->{_STATE} = 'AUTH';
unless ($IMAP_text =~ /\A\Q$IMAP_cond\E \[CAPABILITY /) {
# refresh the CAPABILITY list since the previous one had only pre-login capabilities
@@ -471,7 +476,15 @@ sub new($%) {
$self->capabilities();
}
}
- $self->{_STATE} = 'AUTH';
+ elsif ($IMAP_cond eq 'PREAUTH') {
+ if ($self->{type} eq 'imap' and $self->{STARTTLS} != 0) {
+ $self->fail("PREAUTH greeting on plaintext connection? MiTM in action? Aborting, set \"STARTTLS = NO\" to ignore.");
+ }
+ $self->{_STATE} = 'AUTH';
+ }
+ else {
+ $self->panic();
+ }
# Don't send the COMPRESS command before STARTTLS or AUTH, as per RFC 4978
if ($self->{compress} // 1 and
@@ -506,6 +519,7 @@ sub new($%) {
: ($self->{enable});
if (@extensions) {
$self->fail("Server did not advertise ENABLE (RFC 5161) capability.") unless $self->_capable('ENABLE');
+ $self->fail("Server did not advertise $_ capability.") foreach grep { !$self->_capable($_) } @extensions;
$self->_send('ENABLE '.join(' ',@extensions));
my @enabled = @{$self->{_ENABLED} // []};
$self->fail("Couldn't ENABLE $_") foreach
@@ -1298,7 +1312,8 @@ sub pull_new_messages($$&@) {
# 2^32-1: don't use '*' since the highest UID can be known already
$range .= "$since:4294967295";
- $UIDNEXT = $cache->{UIDNEXT} // $self->panic(); # sanity check
+ $UIDNEXT = $cache->{UIDNEXT} //
+ $self->panic("Unknown UIDNEXT value - non-compliant server?");
$self->fetch($range, "($attrs)", sub($) {
my $mail = shift;
$UIDNEXT = $mail->{UID} + 1 if $UIDNEXT <= $mail->{UID};
@@ -1357,7 +1372,7 @@ sub push_flag_updates($$@) {
$modified->{$uid} //= [ 0, undef ];
} elsif (defined (my $m = $modified->{$uid})) {
# received an untagged FETCH response, remove from the list of pending changes
- # if the flag list was up to date (either implicitely or explicitely)
+ # if the flag list was up to date (either implicitely or explicitly)
if (!defined $m->[1] or $m->[1] eq $flags) {
delete $modified->{$uid};
push @ok, $uid;
@@ -1436,23 +1451,39 @@ my $RE_IPv6 = do {
| (?: (?: $h16 : ){0,6} $h16 )? ::
/x };
+# Parse an IPv4 or IPv6. In list context, return a pair (IP, family),
+# otherwise only the IP. If the argument is not an IP (for instance if
+# it's a hostname), then return (undef, undef) resp. undef. The input
+# can optionaly be enclosed in square brackets which forces its
+# interpretation as an IP literal: an error is raised if it is not the
+# case.
+my $RE_IPv4_anchored = qr/\A($RE_IPv4)\z/;
+my $RE_IPv6_anchored = qr/\A($RE_IPv6)\z/;
+sub _parse_hostip($) {
+ my $v = shift // return;
+ my $literal = $v =~ s/\A\[(.*)\]\z/$1/ ? 1 : 0;
+ my ($ip, $af) = $v =~ $RE_IPv4_anchored ? ($1, AF_INET)
+ : $v =~ $RE_IPv6_anchored ? ($1, AF_INET6)
+ : (undef, undef);
+ die "Invalid IP literal: $v\n" if $literal and !defined($ip);
+ return wantarray ? ($ip, $af) : $ip;
+}
# Opens a TCP socket to the given $host and $port.
sub _tcp_connect($$$) {
my ($self, $host, $port) = @_;
my %hints = (socktype => SOCK_STREAM, protocol => IPPROTO_TCP);
- if ($host =~ qr/\A$RE_IPv4\z/) {
- $hints{family} = AF_INET;
- $hints{flags} |= AI_NUMERICHOST;
- } elsif ($host =~ qr/\A\[($RE_IPv6)\]\z/) {
- $host = $1;
- $hints{family} = AF_INET6;
+ my ($host2, $family) = _parse_hostip($host);
+ if (defined $family) {
+ $hints{family} = $family;
$hints{flags} |= AI_NUMERICHOST;
+ } else {
+ $host2 = $host;
}
- my ($err, @res) = getaddrinfo($host, $port, \%hints);
- $self->fail("Can't getaddrinfo: $err") if $err ne '';
+ my ($err, @res) = getaddrinfo($host2, $port, \%hints);
+ $self->fail("getaddrinfo($host2): $err") if $err ne '';
SOCKETS:
foreach my $ai (@res) {
@@ -1463,9 +1494,9 @@ sub _tcp_connect($$$) {
# https://stackoverflow.com/questions/8284243/how-do-i-set-so-rcvtimeo-on-a-socket-in-perl
my $timeout = pack('l!l!', 30, 0);
setsockopt($s, Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, $timeout)
- or $self->fail("Can't setsockopt SO_RCVTIMEO: $!");
+ or $self->fail("setsockopt SO_RCVTIMEO: $!");
setsockopt($s, Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, $timeout)
- or $self->fail("Can't setsockopt SO_RCVTIMEO: $!");
+ or $self->fail("setsockopt SO_RCVTIMEO: $!");
until (connect($s, $ai->{addr})) {
next if $! == EINTR; # try again if connect(2) was interrupted by a signal
@@ -1482,7 +1513,7 @@ sub _xwrite($$$) {
while ($length > 0) {
my $n = syswrite($_[0], $_[1], $length, $offset);
- $self->fail("Can't write: $!") unless defined $n and $n > 0;
+ $self->fail("write: $!") unless defined $n and $n > 0;
$offset += $n;
$length -= $n;
}
@@ -1494,7 +1525,7 @@ sub _xread($$$) {
my $offset = 0;
my $buf;
while ($length > 0) {
- my $n = sysread($fh, $buf, $length, $offset) // $self->fail("Can't read: $!");
+ my $n = sysread($fh, $buf, $length, $offset) // $self->fail("read: $!");
$self->fail("0 bytes read (got EOF)") unless $n > 0; # EOF
$offset += $n;
$length -= $n;
@@ -1510,7 +1541,7 @@ sub _proxify($$$$) {
$port = getservbyname($port, 'tcp') // $self->fail("Can't getservbyname $port")
unless $port =~ /\A[0-9]+\z/;
- $proxy =~ /\A([A-Za-z0-9]+):\/\/(\P{Control}*\@)?($RE_IPv4|\[$RE_IPv6\]|[^:]+)(:[A-Za-z0-9]+)?\z/
+ $proxy =~ /\A([A-Za-z0-9]+):\/\/(\P{Control}*\@)?([^:]+|\[[^\]]+\])(:[A-Za-z0-9]+)?\z/
or $self->fail("Invalid proxy URI $proxy");
my ($proto, $userpass, $proxyhost, $proxyport) = ($1, $2, $3, $4);
$userpass =~ s/\@\z// if defined $userpass;
@@ -1543,23 +1574,30 @@ sub _proxify($$$$) {
$self->fail('SOCKSv5', 'No acceptable authentication methods');
}
- if ($host !~ /\A(?:$RE_IPv4|\[$RE_IPv6\])\z/ and !$resolv) {
+ my ($hostip, $fam) = _parse_hostip($host);
+ unless (defined($fam) or $resolv) {
# resove the hostname $host locally
my ($err, @res) = getaddrinfo($host, undef, {socktype => SOCK_RAW});
- $self->fail("Can't getaddrinfo: $err") if $err ne '';
- ($host) = first { defined $_ } map {
+ $self->fail("getaddrinfo($host): $err") if $err ne '';
+ my ($addr) = first { defined($_) } map {
my ($err, $ipaddr) = getnameinfo($_->{addr}, NI_NUMERICHOST, NIx_NOSERV);
- $err eq '' ? $ipaddr : undef
+ $err eq '' ? [$ipaddr,$_->{family}] : undef
} @res;
- $self->fail("Can't getnameinfo") unless defined $host;
+ $self->fail("getnameinfo") unless defined $addr;
+ ($hostip, $fam) = @$addr;
}
# send a CONNECT command (CMD 0x01)
- my ($typ, $addr) =
- $host =~ /\A$RE_IPv4\z/ ? (0x01, Socket::inet_pton(AF_INET, $host))
- : ($host =~ /\A\[($RE_IPv6)\]\z/ or $host =~ /\A($RE_IPv6)\z/) ? (0x04, Socket::inet_pton(AF_INET6, $1))
- : (0x03, pack('C',length($host)).$host);
- $self->_xwrite($socket, pack('C4', $v, 0x01, 0x00, $typ).$addr.pack('n', $port));
+ my ($typ, $addr);
+ if (defined $fam) {
+ $typ = $fam == AF_INET ? 0x01 : $fam == AF_INET6 ? 0x04 : $self->panic();
+ $addr = Socket::inet_pton($fam, $hostip);
+ } else {
+ # let the SOCKS server do the resolution
+ $typ = 0x03;
+ $addr = pack('C',length($host)) . $host;
+ }
+ $self->_xwrite($socket, pack('C4', $v, 0x01, 0x00, $typ) . $addr . pack('n', $port));
($v2, my $r, my $rsv, $typ) = unpack('C4', $self->_xread($socket, 4));
$self->fail('SOCKSv5', 'Invalid protocol') unless $v == $v2 and $rsv == 0x00;
@@ -1583,14 +1621,14 @@ sub _proxify($$$$) {
return $socket;
}
else {
- $self->error("Unsupported proxy protocol $proto");
+ $self->fail("Unsupported proxy protocol $proto");
}
}
# $self->_ssl_verify($self, $preverify_ok, $x509_ctx)
# SSL verify callback function, see
-# https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_set_verify.html
+# https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_verify.html
sub _ssl_verify($$$) {
my ($self, $ok, $x509_ctx) = @_;
return 0 unless $x509_ctx; # reject
@@ -1604,7 +1642,7 @@ sub _ssl_verify($$$) {
$self->log(' Subject Name: ', Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert)));
}
- $ok = 1 unless $self->{SSL_verify} // 1;
+ $ok = 1 unless $self->{SSL_verify} // die; # safety check, always set
if ($depth == 0 and !exists $self->{_SSL_PEER_VERIFIED}) {
if ($self->{debug}) {
my $algo = 'sha256';
@@ -1614,15 +1652,23 @@ sub _ssl_verify($$$) {
.$algo.'$'.unpack('H*', Net::SSLeay::X509_digest($cert, $type)));
}
- if (defined (my $fpr = $self->{SSL_fingerprint})) {
- (my $algo, $fpr) = $fpr =~ /^([^\$]+)\$(.*)/ ? ($1, $2) : ('sha256', $fpr);
- my $digest = pack 'H*', ($fpr =~ tr/://rd);
+ if (defined (my $fprs = $self->{SSL_fingerprint})) {
+ my $rv = 0;
+ foreach my $fpr (split /\s+/, $fprs) {
+ (my $algo, $fpr) = $fpr =~ /^([^\$]+)\$(.*)/ ? ($1, $2) : ('sha256', $fpr);
+ my $digest = pack 'H*', ($fpr =~ tr/://rd);
- my $type = Net::SSLeay::EVP_get_digestbyname($algo)
- or $self->_ssl_error("Can't find MD value for name '$algo'");
+ my $type = Net::SSLeay::EVP_get_digestbyname($algo)
+ or $self->_ssl_error("Can't find MD value for name '$algo'");
- my $pkey = Net::SSLeay::X509_get_X509_PUBKEY($cert);
- unless (defined $pkey and Net::SSLeay::EVP_Digest($pkey, $type) eq $digest) {
+ my $pkey = Net::SSLeay::X509_get_X509_PUBKEY($cert);
+ if (defined $pkey and Net::SSLeay::EVP_Digest($pkey, $type) eq $digest) {
+ $self->log('Peer certificate matches pinned SPKI digest ', $algo .'$'. $fpr) if $self->{debug};
+ $rv = 1;
+ last;
+ }
+ }
+ unless ($rv) {
$self->warn("Fingerprint doesn't match! MiTM in action?");
$ok = 0;
}
@@ -1633,7 +1679,7 @@ sub _ssl_verify($$$) {
}
my %SSL_proto;
-BEGIN {
+BEGIN { # TODO deprecated, remove in 0.6
sub _append_ssl_proto($$) {
my ($k, $v) = @_;
$SSL_proto{$k} = $v if defined $v;
@@ -1646,77 +1692,141 @@ BEGIN {
_append_ssl_proto( "TLSv1.3", eval { Net::SSLeay::OP_NO_TLSv1_3() } );
}
+# see ssl/ssl_conf.c:protocol_from_string() in the OpenSSL source tree
+my %SSL_protocol_versions = (
+ "SSLv3" => eval { Net::SSLeay::SSL3_VERSION() }
+ , "TLSv1" => eval { Net::SSLeay::TLS1_VERSION() }
+ , "TLSv1.1" => eval { Net::SSLeay::TLS1_1_VERSION() }
+ , "TLSv1.2" => eval { Net::SSLeay::TLS1_2_VERSION() }
+ , "TLSv1.3" => eval { Net::SSLeay::TLS1_3_VERSION() }
+);
+
# $self->_start_ssl($socket)
# Upgrade the $socket to SSL/TLS.
sub _start_ssl($$) {
my ($self, $socket) = @_;
- my $ctx = Net::SSLeay::CTX_new() or $self->panic("Failed to create SSL_CTX $!");
- my $ssl_options = Net::SSLeay::OP_SINGLE_DH_USE() | Net::SSLeay::OP_SINGLE_ECDH_USE();
+ # need OpenSSL 1.1.0 or later for SSL_CTX_set_min_proto_version(3ssl), see
+ # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_min_proto_version.html
+ $self->panic("SSL/TLS functions require OpenSSL 1.1.0 or later")
+ if Net::SSLeay::OPENSSL_VERSION_NUMBER() < 0x1010000f;
+
+ my $ctx = Net::SSLeay::CTX_new() or $self->panic("SSL_CTX_new(): $!");
+ $self->{SSL_verify} //= 1; # default is to perform certificate verification
+
+ if (defined $self->{_OUTBUF} and $self->{_OUTBUF} ne '') {
+ $self->warn("Truncating non-empty output buffer (unauthenticated response injection?)");
+ undef $self->{_OUTBUF};
+ }
- $self->{SSL_protocols} //= q{!SSLv2 !SSLv3 !TLSv1 !TLSv1.1};
- my ($proto_include, $proto_exclude) = (0, 0);
- foreach (split /\s+/, $self->{SSL_protocols}) {
- my $neg = s/^!// ? 1 : 0;
- s/\.0$//;
- ($neg ? $proto_exclude : $proto_include) |= $SSL_proto{$_} // $self->panic("Unknown SSL protocol: $_");
- }
- if ($proto_include != 0) {
- # exclude all protocols except those explictly included
- my $x = 0;
- $x |= $_ foreach values %SSL_proto;
- $x &= ~ $proto_include;
- $proto_exclude |= $x;
- }
- my @proto_exclude = grep { ($proto_exclude & $SSL_proto{$_}) != 0 } keys %SSL_proto;
- $self->log("Disabling SSL protocols: ".join(', ', sort @proto_exclude)) if $self->{debug};
- $ssl_options |= $SSL_proto{$_} foreach @proto_exclude;
+ my $ssl_options = Net::SSLeay::OP_SINGLE_DH_USE() | Net::SSLeay::OP_SINGLE_ECDH_USE();
$ssl_options |= Net::SSLeay::OP_NO_COMPRESSION();
- # https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_set_options.html
+ if (defined $self->{SSL_protocol_min} or defined $self->{SSL_protocol_max}) {
+ my ($min, $max) = @$self{qw/SSL_protocol_min SSL_protocol_max/};
+ if (defined $min) {
+ my $v = $SSL_protocol_versions{$min} // $self->panic("Unknown protocol version: $min");
+ $self->_ssl_error("CTX_set_min_proto_version()") unless Net::SSLeay::CTX_set_min_proto_version($ctx, $v) == 1;
+ $self->log("Minimum SSL/TLS protocol version: ", $min) if $self->{debug};
+ }
+ if (defined $max) {
+ my $v = $SSL_protocol_versions{$max} // $self->panic("Unknown protocol version: $max");
+ $self->_ssl_error("CTX_set_max_proto_version()") unless Net::SSLeay::CTX_set_max_proto_version($ctx, $v) == 1;
+ $self->log("Maximum SSL/TLS protocol version: ", $max) if $self->{debug};
+ }
+ } elsif (defined (my $protos = $self->{SSL_protocols})) { # TODO deprecated, remove in 0.6
+ $self->warn("SSL_protocols is deprecated and will be removed in a future release! " .
+ "Use SSL_protocol_{min,max} instead.");
+ my ($proto_include, $proto_exclude) = (0, 0);
+ foreach (split /\s+/, $protos) {
+ my $neg = s/^!// ? 1 : 0;
+ s/\.0$//;
+ ($neg ? $proto_exclude : $proto_include) |= $SSL_proto{$_} // $self->panic("Unknown SSL protocol: $_");
+ }
+ if ($proto_include != 0) {
+ # exclude all protocols except those explictly included
+ my $x = 0;
+ $x |= $_ foreach values %SSL_proto;
+ $x &= ~ $proto_include;
+ $proto_exclude |= $x;
+ }
+ my @proto_exclude = grep { ($proto_exclude & $SSL_proto{$_}) != 0 } keys %SSL_proto;
+ $self->log("Disabling SSL protocols: ".join(', ', sort @proto_exclude)) if $self->{debug};
+ $ssl_options |= $SSL_proto{$_} foreach @proto_exclude;
+ }
+
+ # https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_options.html
+ # TODO 0.6: move SSL_CTX_set_options() and SSL_CTX_set_mode() before SSL_CTX_set_{min,max}_proto_version()
Net::SSLeay::CTX_set_options($ctx, $ssl_options);
- # https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_set_mode.html
+ # https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_mode.html
Net::SSLeay::CTX_set_mode($ctx,
Net::SSLeay::MODE_ENABLE_PARTIAL_WRITE() |
Net::SSLeay::MODE_ACCEPT_MOVING_WRITE_BUFFER() |
- Net::SSLeay::MODE_AUTO_RETRY() | # don't fail SSL_read on renegociation
+ Net::SSLeay::MODE_AUTO_RETRY() | # don't fail SSL_read on renegotiation
Net::SSLeay::MODE_RELEASE_BUFFERS() );
- if (defined (my $ciphers = $self->{SSL_cipherlist})) {
- Net::SSLeay::CTX_set_cipher_list($ctx, $ciphers)
- or $self->_ssl_error("Can't set cipher list");
+ if (defined (my $str = $self->{SSL_cipherlist})) {
+ $self->_ssl_error("SSL_CTX_set_cipher_list()") unless Net::SSLeay::CTX_set_cipher_list($ctx, $str) == 1;
}
+ if (defined (my $str = $self->{SSL_ciphersuites})) {
+ $self->_ssl_error("SSL_CTX_set_ciphersuites()") unless Net::SSLeay::CTX_set_ciphersuites($ctx, $str) == 1;
+ }
+
+ my $vpm = Net::SSLeay::X509_VERIFY_PARAM_new() or $self->_ssl_error("X509_VERIFY_PARAM_new()");
+ my $purpose = Net::SSLeay::X509_PURPOSE_SSL_SERVER();
+ $self->_ssl_error("X509_VERIFY_PARAM_set_purpose()") unless Net::SSLeay::X509_VERIFY_PARAM_set_purpose($vpm, $purpose) == 1;
+ $self->_ssl_error("CTX_set_purpose()") unless Net::SSLeay::CTX_set_purpose($ctx, $purpose) == 1;
- if ($self->{SSL_verify} // 1) {
- # verify the certificate chain
- my ($file, $path) = ($self->{SSL_CAfile} // '', $self->{SSL_CApath} // '');
- if ($file ne '' or $path ne '') {
- Net::SSLeay::CTX_load_verify_locations($ctx, $file, $path)
- or $self->_ssl_error("Can't load verify locations");
+ my $host = $self->{host} // $self->panic();
+ my ($hostip, $hostipfam) = _parse_hostip($host);
+ if ($self->{SSL_verify}) {
+ # verify certificate chain
+ if (defined $self->{SSL_CAfile} or defined $self->{SSL_CApath}) {
+ $self->_ssl_error("SSL_CTX_load_verify_locations()")
+ unless Net::SSLeay::CTX_load_verify_locations($ctx,
+ $self->{SSL_CAfile} // '', $self->{SSL_CApath} // '') == 1;
+ } else {
+ $self->log("Using default locations for trusted CA certificates") if $self->{debug};
+ $self->_ssl_error("SSL_CTX_set_default_verify_paths()")
+ unless Net::SSLeay::CTX_set_default_verify_paths($ctx) == 1;
+ }
+
+ # verify DNS hostname or IP literal
+ if (defined $hostipfam) {
+ my $addr = Socket::inet_pton($hostipfam, $hostip) // $self->panic();
+ $self->_ssl_error("X509_VERIFY_PARAM_set1_ip()")
+ unless Net::SSLeay::X509_VERIFY_PARAM_set1_ip($vpm, $addr) == 1;
+ } else {
+ $self->_ssl_error("X509_VERIFY_PARAM_set1_host()")
+ unless Net::SSLeay::X509_VERIFY_PARAM_set1_host($vpm, $host) == 1;
}
}
else {
Net::SSLeay::CTX_set_verify_depth($ctx, 0);
}
- Net::SSLeay::CTX_set_purpose($ctx, Net::SSLeay::X509_PURPOSE_SSL_SERVER())
- or $self->_ssl_error("Can't set purpose");
Net::SSLeay::CTX_set_verify($ctx, Net::SSLeay::VERIFY_PEER(), sub($$) {$self->_ssl_verify(@_)});
+ $self->_ssl_error("CTX_SSL_set1_param()") unless Net::SSLeay::CTX_set1_param($ctx, $vpm) == 1;
+
+ my $ssl = Net::SSLeay::new($ctx) or $self->fail("SSL_new()");
+ $self->fail("SSL_set_fd()") unless Net::SSLeay::set_fd($ssl, fileno($socket)) == 1;
+
+ # always use 'SSL_hostname' when set, otherwise use 'host' (unless it's an IP)
+ my $servername = $self->{SSL_hostname} // (defined $hostipfam ? "" : $host);
+ if ($servername ne "") {
+ $self->_ssl_error("SSL_set_tlsext_host_name($servername)")
+ unless Net::SSLeay::set_tlsext_host_name($ssl, $servername) == 1;
+ $self->log("Using SNI with name $servername") if $self->{debug};
+ }
- my $ssl = Net::SSLeay::new($ctx) or $self->fail("Can't create new SSL structure");
- Net::SSLeay::set_fd($ssl, fileno $socket) or $self->fail("SSL filehandle association failed");
$self->_ssl_error("Can't initiate TLS/SSL handshake") unless Net::SSLeay::connect($ssl) == 1;
- $self->panic("Couldn't verify") unless $self->{_SSL_PEER_VERIFIED}; # sanity check
+ $self->panic() unless $self->{_SSL_PEER_VERIFIED}; # sanity check
+ $self->panic() if $self->{SSL_verify} and Net::SSLeay::get_verify_result($ssl) != Net::SSLeay::X509_V_OK();
+ Net::SSLeay::X509_VERIFY_PARAM_free($vpm);
if ($self->{debug}) {
- my $v = Net::SSLeay::version($ssl);
- $self->log(sprintf('SSL protocol: %s (0x%x)', ($v == 0x0002 ? 'SSLv2' :
- $v == 0x0300 ? 'SSLv3' :
- $v == 0x0301 ? 'TLSv1' :
- $v == 0x0302 ? 'TLSv1.1' :
- $v == 0x0303 ? 'TLSv1.2' :
- $v == 0x0304 ? 'TLSv1.3' :
- '??'),
- $v));
+ $self->log(sprintf('SSL protocol: %s (0x%x)',
+ , Net::SSLeay::get_version($ssl)
+ , Net::SSLeay::version($ssl)));
$self->log(sprintf('SSL cipher: %s (%d bits)'
, Net::SSLeay::get_cipher($ssl)
, Net::SSLeay::get_cipher_bits($ssl)));
@@ -1751,7 +1861,7 @@ sub _getline($;$) {
$n = sysread($stdout, $buf, $BUFSIZE, 0);
}
- $self->_ssl_error("Can't read: $!") unless defined $n;
+ $self->_ssl_error("read: $!") unless defined $n;
$self->_ssl_error("0 bytes read (got EOF)") unless $n > 0; # EOF
$self->{_OUTRAWCOUNT} += $n;
@@ -1964,7 +2074,7 @@ sub _cmd_flush($;$$) {
my $written = defined $ssl ?
Net::SSLeay::write_partial($ssl, $offset, $length, $self->{_INBUF}) :
syswrite($stdin, $self->{_INBUF}, $length, $offset);
- $self->_ssl_error("Can't write: $!") unless defined $written and $written > 0;
+ $self->_ssl_error("write: $!") unless defined $written and $written > 0;
$offset += $written;
$length -= $written;
diff --git a/pullimap b/pullimap
index ee2168d..d4aebf9 100755
--- a/pullimap
+++ b/pullimap
@@ -2,7 +2,7 @@
#----------------------------------------------------------------------
# Pull mails from an IMAP mailbox and deliver them to an SMTP session
-# Copyright © 2016-2019 Guilhem Moulin <guilhem@fripost.org>
+# Copyright © 2016-2022 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,14 +15,14 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
#----------------------------------------------------------------------
use v5.20.2;
use strict;
use warnings;
-our $VERSION = '0.4';
+our $VERSION = '0.5.8';
my $NAME = 'pullimap';
use Errno 'EINTR';
@@ -31,8 +31,8 @@ use Getopt::Long qw/:config posix_default no_ignore_case gnu_getopt auto_version
use List::Util 'first';
use Socket qw/PF_INET PF_INET6 SOCK_STREAM IPPROTO_TCP/;
-use lib 'lib';
-use Net::IMAP::InterIMAP 0.0.5 qw/xdg_basedir read_config compact_set/;
+use lib "./lib";
+use Net::IMAP::InterIMAP 0.5.8 qw/xdg_basedir read_config compact_set/;
# Clean up PATH
$ENV{PATH} = join ':', qw{/usr/bin /bin};
@@ -105,7 +105,7 @@ do {
# Read a UID (32-bits integer) from the statefile, or undef if we're at
# the end of the statefile
sub readUID() {
- my $n = sysread($STATE, my $buf, 4) // die "Can't sysread: $!";
+ my $n = sysread($STATE, my $buf, 4) // die "read: $!";
return if $n == 0; # EOF
# file length is a multiple of 4 bytes, and we always read 4 bytes at a time
die "Corrupted state file!" if $n != 4;
@@ -118,7 +118,7 @@ sub writeUID($) {
my $offset = 0;
for ( my $offset = 0
; $offset < 4
- ; $offset += syswrite($STATE, $uid, 4-$offset, $offset) // die "Can't syswrite: $!"
+ ; $offset += syswrite($STATE, $uid, 4-$offset, $offset) // die "write: $!"
) {}
# no need to sync (or flush) since $STATE is opened with O_DSYNC
}
@@ -334,11 +334,11 @@ sub pull(;$) {
undef $SMTP;
# update the statefile
- my $p = sysseek($STATE, 4, SEEK_SET) // die "Can't seek: $!";
+ my $p = sysseek($STATE, 4, SEEK_SET) // die "seek: $!";
die "Couldn't seek to 4" unless $p == 4; # safety check
my ($uidnext) = $IMAP->get_cache('UIDNEXT');
writeUID($uidnext);
- truncate($STATE, 8) // die "Can't truncate";
+ truncate($STATE, 8) // die "truncate: $!";
}
do {
diff --git a/pullimap.sample b/pullimap.sample
index 136d3d4..f1a66f9 100644
--- a/pullimap.sample
+++ b/pullimap.sample
@@ -4,10 +4,8 @@ deliver-method = smtp:[127.0.0.1]:25
purge-after = 90
# SSL options
-SSL_CApath = /etc/ssl/certs
#SSL_verify = YES
-#SSL_protocols = !SSLv2 !SSLv3 !TLSv1 !TLSv1.1
-#SSL_cipherlist = EECDH+AESGCM:!MEDIUM:!LOW:!EXP:!aNULL:!eNULL
+#SSL_protocol_min = TLSv1.2
[private]
#type = imaps
diff --git a/pullimap@.service b/pullimap@.service
index a9ce09a..4f9518b 100644
--- a/pullimap@.service
+++ b/pullimap@.service
@@ -1,11 +1,12 @@
[Unit]
Description=Pull mails from an IMAP mailbox and deliver them to an SMTP session (instance %i)
Documentation=man:pullimap(1)
+Documentation=https://guilhem.org/interimap/pullimap.1.html
Wants=network-online.target
After=network-online.target
[Service]
-ExecStart=/usr/bin/pullimap --idle %i
+ExecStart=@bindir@/pullimap --idle %i
RestartSec=2min
Restart=always
diff --git a/tests/auth-login/t b/tests/auth-login/t
index 7fd83d5..38e2028 100644
--- a/tests/auth-login/t
+++ b/tests/auth-login/t
@@ -9,4 +9,4 @@ grep -Fx "remote: C: xxx LOGIN [REDACTED]" <"$STDERR" || error
check_mailbox_status "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/auth-logindisabled/remote.conf b/tests/auth-logindisabled/remote.conf
index 1f02afe..990dcbd 100644
--- a/tests/auth-logindisabled/remote.conf
+++ b/tests/auth-logindisabled/remote.conf
@@ -1,4 +1,6 @@
!include conf.d/imapd.conf
# trick dovecot into treating local connections as insecure
-imap_capability = +LOGINDISABLED
+imap_capability {
+ LOGINDISABLED = yes
+}
diff --git a/tests/auth-logindisabled/t b/tests/auth-logindisabled/t
index 0bcd0d6..402355f 100644
--- a/tests/auth-logindisabled/t
+++ b/tests/auth-logindisabled/t
@@ -13,4 +13,4 @@ grep -Fx "LOGINDISABLED" <"$TMPDIR/capabilities" || error
grep -Fx "remote: ERROR: Logins are disabled." <"$STDERR" || error
! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/auth-noplaintext/t b/tests/auth-noplaintext/t
index 11d7d4d..862bc8d 100644
--- a/tests/auth-noplaintext/t
+++ b/tests/auth-noplaintext/t
@@ -12,4 +12,4 @@ tr " " "\\n" <"$TMPDIR/capability" >"$TMPDIR/capabilities"
grep -Fx "remote: ERROR: Server did not advertise STARTTLS capability." <"$STDERR" || error
! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/auth-sasl-plain-no-ir/t b/tests/auth-sasl-plain-no-ir/t
index 17aa9e6..f236ac7 100644
--- a/tests/auth-sasl-plain-no-ir/t
+++ b/tests/auth-sasl-plain-no-ir/t
@@ -23,4 +23,4 @@ xcgrep "$n" -E "^remote(\(INBOX\))?: C: [0-9]+ APPEND INBOX .* \{[0-9]+\}$" <"$S
check_mailbox_status "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/auth-sasl-plain/t b/tests/auth-sasl-plain/t
index 68f71a9..c5cb024 100644
--- a/tests/auth-sasl-plain/t
+++ b/tests/auth-sasl-plain/t
@@ -9,4 +9,4 @@ grep -Fx "remote: C: xxx AUTHENTICATE PLAIN [REDACTED]" <"$STDERR" || error
check_mailbox_status "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/certs/.gitignore b/tests/certs/.gitignore
new file mode 100644
index 0000000..8b2d0ad
--- /dev/null
+++ b/tests/certs/.gitignore
@@ -0,0 +1,4 @@
+!/generate
+/*.key
+/*.crt
+/*.pem
diff --git a/tests/certs/generate b/tests/certs/generate
new file mode 100755
index 0000000..f449764
--- /dev/null
+++ b/tests/certs/generate
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+set -ue
+PATH="/usr/bin:/bin"
+export PATH
+
+BASEDIR="$(dirname -- "$0")"
+OU="InterIMAP test suite"
+cd "$BASEDIR"
+
+OPENSSL_CONF="./openssl.cnf"
+export OPENSSL_CONF
+
+cadir="$(mktemp --tmpdir --directory)"
+trap 'rm -rf -- "$cadir"' EXIT INT TERM
+genpkey() {
+ local key="$1"
+ shift
+ openssl genpkey -out "$key" "$@" 2>&1
+}
+
+# generate CA (we intentionally throw away the private key and serial
+# file to avoid reuse)
+genpkey "$cadir/ca.key" -algorithm RSA
+openssl req -new -x509 -rand /dev/urandom \
+ -subj "/OU=$OU/CN=Fake Root CA" \
+ -addext subjectKeyIdentifier="hash" \
+ -addext authorityKeyIdentifier="keyid:always,issuer" \
+ -addext basicConstraints="critical,CA:TRUE" \
+ -key "$cadir/ca.key" -out ./ca.crt
+
+SERIAL=1
+new() {
+ local key="$1" cn="$2"
+ openssl req -new -rand /dev/urandom -key "$key" \
+ -subj "/OU=$OU/CN=$cn" ${3+-addext subjectAltName="$3"} \
+ -out "$cadir/new.csr"
+ cat >"$cadir/new-ext.cnf" <<-EOF
+ basicConstraints = critical, CA:FALSE
+ keyUsage = critical, digitalSignature, keyEncipherment
+ extendedKeyUsage = critical, serverAuth
+ EOF
+ if [ -n "${3+x}" ]; then
+ printf "subjectAltName = %s\\n" "$3" >>"$cadir/new-ext.cnf"
+ fi
+ openssl x509 -req -in "$cadir/new.csr" -CA ./ca.crt -CAkey "$cadir/ca.key" \
+ -CAserial "$cadir/ca.srl" -CAcreateserial -extfile "$cadir/new-ext.cnf" 2>&1
+}
+
+genpkey ./dovecot.rsa.key -algorithm RSA
+new ./dovecot.rsa.key "localhost" "DNS:localhost,DNS:ip6-localhost,IP:127.0.0.1,IP:::1" >./dovecot.rsa.crt
+
+genpkey ./dovecot.ecdsa.key -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve
+new ./dovecot.ecdsa.key "localhost" >./dovecot.ecdsa.crt
+
+genpkey ./dovecot.rsa2.key -algorithm RSA
+new ./dovecot.rsa2.key "imap.example.net" "DNS:imap.example.net,DNS:localhost" >./dovecot.rsa2.crt
diff --git a/tests/certs/openssl.cnf b/tests/certs/openssl.cnf
new file mode 100644
index 0000000..b1af7b8
--- /dev/null
+++ b/tests/certs/openssl.cnf
@@ -0,0 +1,4 @@
+[ req ]
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
diff --git a/tests/compress/t b/tests/compress/t
index 5625761..0a04a73 100644
--- a/tests/compress/t
+++ b/tests/compress/t
@@ -16,4 +16,4 @@ echo "compress = no" >>"$XDG_CONFIG_HOME/interimap/config"
interimap --debug || error
! grep -E "^remote: C: [^[:blank:]]+ COMPRESS DEFLATE$" <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/condstore/t b/tests/condstore/t
index 1963b2b..303287e 100644
--- a/tests/condstore/t
+++ b/tests/condstore/t
@@ -28,7 +28,7 @@ while [ $(date +%s) -le $timer ]; do
u="$(shuf -n1 -e "local" "remote")"
f="$(shuf -n1 -e "${FLAGS[@]}")"
seqs="$(shuf -n$((N/8)) -i1-$N)" # trigger changes on 1/8 of all messages
- doveadm -u "$u" flags "$a" "$f" mailbox "INBOX" "${seqs//$'\n'/,}"
+ doveadm -u "$u" flags "$a" --no-userdb-lookup "$f" mailbox "INBOX" "${seqs//$'\n'/,}"
sleep "0.0$(shuf -n1 -i10-99)" # 10 to 99ms
done
sleep 5
@@ -39,7 +39,7 @@ trap - EXIT INT TERM
# make sure the list of uids for a given tag match
flagged_uids() {
local u="$1" f="$2"
- doveadm -u "$u" search mailbox "INBOX" keyword "$f" | cut -d" " -f2 | sort -n
+ doveadm -u "$u" search --no-userdb-lookup mailbox "INBOX" keyword "$f" | cut -d" " -f2 | sort -n
}
for f in "${FLAGS[@]}"; do
diff --label="local/$f" --label="remote/$f" -u -- \
@@ -47,4 +47,4 @@ for f in "${FLAGS[@]}"; do
error "UID list differs for keyword '$f'"
done
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/snippets/dovecot/dhparams.pem b/tests/config/dovecot/dhparams.pem
index 7734d2a..7734d2a 100644
--- a/tests/snippets/dovecot/dhparams.pem
+++ b/tests/config/dovecot/dhparams.pem
diff --git a/tests/snippets/dovecot/imapd.conf b/tests/config/dovecot/imapd.conf
index 2b26451..6f456f6 100644
--- a/tests/snippets/dovecot/imapd.conf
+++ b/tests/config/dovecot/imapd.conf
@@ -1,8 +1,5 @@
-protocols = $protocols imap
-
-mail_plugins = $mail_plugins zlib
-protocol imap {
- mail_plugins = $mail_plugins imap_zlib
+protocols {
+ imap = yes
}
service imap-login {
diff --git a/tests/snippets/dovecot/interimap-required-capabilities.conf b/tests/config/dovecot/interimap-required-capabilities.conf
index 10dd8e1..10dd8e1 100644
--- a/tests/snippets/dovecot/interimap-required-capabilities.conf
+++ b/tests/config/dovecot/interimap-required-capabilities.conf
diff --git a/tests/snippets/dovecot/lmtpd.conf b/tests/config/dovecot/lmtpd.conf
index 6aa8365..23cdc1e 100644
--- a/tests/snippets/dovecot/lmtpd.conf
+++ b/tests/config/dovecot/lmtpd.conf
@@ -1,4 +1,6 @@
-protocols = $protocols lmtp
+protocols {
+ lmtp = yes
+}
service lmtp {
inet_listener lmtp {
diff --git a/tests/config/dovecot/ssl.conf b/tests/config/dovecot/ssl.conf
new file mode 100644
index 0000000..fd119cd
--- /dev/null
+++ b/tests/config/dovecot/ssl.conf
@@ -0,0 +1,6 @@
+ssl = required
+ssl_server_cert_file = dovecot.rsa.crt
+ssl_server_key_file = dovecot.rsa.key
+ssl_server_dh_file = dhparams.pem
+ssl_min_protocol = TLSv1.2
+ssl_cipher_list = DEFAULT@SECLEVEL=2
diff --git a/tests/db-exclusive-lock/t b/tests/db-exclusive-lock/t
index 88172c9..0d0badb 100644
--- a/tests/db-exclusive-lock/t
+++ b/tests/db-exclusive-lock/t
@@ -10,7 +10,7 @@ sleep .5
# subsequent runs fail as we can't acquire the exclusive lock
! interimap || error
-grep -Fx "DBD::SQLite::db do failed: database is locked at ./interimap line 177." <"$STDERR" \
- || error "Is \$DBH->do(\"PRAGMA locking_mode = EXCLUSIVE\"); at line 177?"
+grep -Ex "DBD::SQLite::db do failed: database is locked at (\S+/)?interimap line 181\." <"$STDERR" \
+ || error "Is \$DBH->do(\"PRAGMA locking_mode = EXCLUSIVE\"); at line 181?"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/db-migration-0-1-foreign-key-violation/t b/tests/db-migration-0-1-foreign-key-violation/t
index 35e5be5..fa4afb4 100644
--- a/tests/db-migration-0-1-foreign-key-violation/t
+++ b/tests/db-migration-0-1-foreign-key-violation/t
@@ -18,4 +18,4 @@ EOF
grep -Fx "Upgrading database version from 0" <"$STDERR" || error "DB upgrade not attempted"
grep -Fx "database: ERROR: Broken referential integrity! Refusing to commit changes." <"$STDERR" || error "DB upgrade successful despite broken refint"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/db-no-create--watch/t b/tests/db-no-create--watch/t
index 89f1e3e..3097558 100644
--- a/tests/db-no-create--watch/t
+++ b/tests/db-no-create--watch/t
@@ -1,6 +1,6 @@
! interimap --watch=60 || error
-grep -Ex "DBI connect\(.*\) failed: unable to open database file at \./interimap line 173\." <"$STDERR" || error
+grep -Ex "DBI connect\(.*\) failed: unable to open database file at (\S+/)?interimap line 177\." <"$STDERR" || error
test \! -e "$XDG_DATA_HOME/interimap/remote.db" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/db-upgrade-0-1-delim-mismatch/t b/tests/db-upgrade-0-1-delim-mismatch/t
index d133437..c15927c 100644
--- a/tests/db-upgrade-0-1-delim-mismatch/t
+++ b/tests/db-upgrade-0-1-delim-mismatch/t
@@ -4,4 +4,4 @@ sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql" || error "Co
grep -Fx 'ERROR: Local and remote hierachy delimiters differ (local "\"", remote "^"), refusing to update table `mailboxes`.' <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/db-upgrade-0-1/t b/tests/db-upgrade-0-1/t
index 088008e..2d0546c 100644
--- a/tests/db-upgrade-0-1/t
+++ b/tests/db-upgrade-0-1/t
@@ -1,6 +1,6 @@
# create the mailboxes from the database
-doveadm -u "local" mailbox create "a.b1.c1" "a.b1.c2" "a.b2.c" "a2"
-doveadm -u "remote" mailbox create "a.b1.c1" "a.b1.c2" "a.b2.c" "a2"
+doveadm -u "local" mailbox create --no-userdb-lookup "a.b1.c1" "a.b1.c2" "a.b2.c" "a2"
+doveadm -u "remote" mailbox create --no-userdb-lookup "a.b1.c1" "a.b1.c2" "a.b2.c" "a2"
# import an existing non-migrated database
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql" || error "Couldn't import DB"
@@ -31,4 +31,4 @@ diff -u --label="a/dump.sql" --label="b/dump.sql" \
"$TMPDIR/dump-expected.sql" "$TMPDIR/dump.sql" \
|| error "DB dumps differ"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/delete/t b/tests/delete/t
index c38d4d3..3baca2c 100644
--- a/tests/delete/t
+++ b/tests/delete/t
@@ -1,4 +1,4 @@
-doveadm -u "local" mailbox create "foo.bar" "foo.bar.baz"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo.bar" "foo.bar.baz"
for m in "foo.bar" "foo.bar.baz" "INBOX"; do
sample_message | deliver -u "local" -- -m "$m"
@@ -48,14 +48,14 @@ EOF
diff -u --label="a/dump.sql" --label="b/dump.sql" \
"$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" || error "SQL dumps differ"
-! doveadm -u "local" mailbox status uidvalidity "foo.bar" # gone
- doveadm -u "remote" mailbox status uidvalidity "foo^bar"
+! doveadm -u "local" mailbox status --no-userdb-lookup uidvalidity "foo.bar" # gone
+ doveadm -u "remote" mailbox status --no-userdb-lookup uidvalidity "foo^bar"
# now delete from the remote server and the database
interimap --delete "foo.bar"
-! doveadm -u "local" mailbox status uidvalidity "foo.bar"
-! doveadm -u "remote" mailbox status uidvalidity "foo^bar"
+! doveadm -u "local" mailbox status --no-userdb-lookup uidvalidity "foo.bar"
+! doveadm -u "remote" mailbox status --no-userdb-lookup uidvalidity "foo^bar"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.sql" <<-EOF
SELECT idx, mailbox FROM mailboxes ORDER BY idx
@@ -80,8 +80,8 @@ step_done
step_start "\\Noinferiors mailbox"
interimap --delete "foo.bar.baz"
-! doveadm -u "local" mailbox status uidvalidity "foo.bar.baz"
-! doveadm -u "remote" mailbox status uidvalidity "foo^bar^baz"
+! doveadm -u "local" mailbox status --no-userdb-lookup uidvalidity "foo.bar.baz"
+! doveadm -u "remote" mailbox status --no-userdb-lookup uidvalidity "foo^bar^baz"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
SELECT COUNT(*) FROM mailboxes
@@ -92,4 +92,4 @@ check_mailbox_list
check_mailboxes_status "INBOX"
step_done
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/delimiter-change/t b/tests/delimiter-change/t
index 3f96953..72229de 100644
--- a/tests/delimiter-change/t
+++ b/tests/delimiter-change/t
@@ -1,5 +1,5 @@
# create and populate some mailboxes
-doveadm -u "local" mailbox create -- "foo" "foo.bar" "baz"
+doveadm -u "local" mailbox create --no-userdb-lookup -- "foo" "foo.bar" "baz"
run() {
local i m u s1="$1" s2="$2" m2
@@ -31,7 +31,7 @@ sed -ri "s,^(\\s*separator\\s*)=.*,separator = .," "$HOME_remote/.dovecot/config
run "." "."
# ensure there were no duplicates
-n="$(doveadm -u "local" search all | wc -l)"
+n="$(doveadm -u "local" search --no-userdb-lookup all | wc -l)"
[ "$n" -eq 64 ] || error "$n != 64"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/ignore-mailbox/t b/tests/ignore-mailbox/t
index f90227c..1b09a1d 100644
--- a/tests/ignore-mailbox/t
+++ b/tests/ignore-mailbox/t
@@ -1,5 +1,5 @@
-doveadm -u "local" mailbox create "foo" -- "-virtual"
-doveadm -u "remote" mailbox create "bar" -- "virtual-"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo" -- "-virtual"
+doveadm -u "remote" mailbox create --no-userdb-lookup "bar" -- "virtual-"
interimap_init
check_mailbox_list
@@ -15,8 +15,8 @@ for ((i = 0; i < 16; i++)); do
done
# create new mailboxes matching 'ignore-mailbox'
-doveadm -u "local" mailbox create "virtual" "virtual.foo"
-doveadm -u "remote" mailbox create "virtual^bar"
+doveadm -u "local" mailbox create --no-userdb-lookup "virtual" "virtual.foo"
+doveadm -u "remote" mailbox create --no-userdb-lookup "virtual^bar"
for n in $(seq 1 "$(shuf -n1 -i1-8)"); do
sample_message | deliver -u "local" -- -m "virtual"
sample_message | deliver -u "local" -- -m "virtual.foo"
@@ -59,4 +59,4 @@ EOF
check_mailboxes_status "virtual" "virtual.bar" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/list b/tests/interimap.list
index 402ec51..559daed 100644
--- a/tests/list
+++ b/tests/interimap.list
@@ -38,6 +38,7 @@ repair --repair
auth-login LOGIN
auth-logindisabled LOGINDISABLED
auth-noplaintext abort when STARTTLS is not offered
+ preauth-plaintext abort on MiTM via PREAUTH greeting
compress COMPRESS=DEFLATE
condstore CONDSTORE
@@ -46,16 +47,17 @@ split-set Split large sets to avoid extra-long command lines
. SSL/TLS
starttls-logindisabled LOGINDISABLED STARTTLS
starttls STARTTLS
+ starttls-injection STARTTLS response injection
tls SSL/TLS handshake
... tls-verify-peer
tls-pin-fingerprint pubkey fingerprint pinning
+ tls-rsa+ecdsa pubkey fingerprint pinning for dual-cert RSA+ECDSA
+ tls-sni TLS servername extension (SNI)
tls-protocols force TLS protocol versions
+ tls-ciphers force TLS cipher list/suites
. Live synchronization (60s)
sync-live local/remote simulation
sync-live-crippled local/remote simulation (crippled remote)
sync-live-tls local/remote simulation (TLS remote)
sync-live-multi local/remote1+remote2+remote3 simulation (3 local namespaces)
-
-. pullimap
- ... pullimap
diff --git a/tests/largeint/t b/tests/largeint/t
index b0877d5..84d21ae 100644
--- a/tests/largeint/t
+++ b/tests/largeint/t
@@ -1,15 +1,15 @@
-doveadm -u "local" mailbox create "foo" "bar" "baz"
-doveadm -u "remote" mailbox create "foo" "bar" "baz"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo" "bar" "baz"
+doveadm -u "remote" mailbox create --no-userdb-lookup "foo" "bar" "baz"
-doveadm -u "local" mailbox update --uid-validity 1 "INBOX"
-doveadm -u "local" mailbox update --uid-validity 2147483647 "foo" # 2^31-1
-doveadm -u "local" mailbox update --uid-validity 2147483648 "bar" # 2^31
-doveadm -u "local" mailbox update --uid-validity 4294967295 "baz" # 2^32-1
+doveadm -u "local" mailbox update --no-userdb-lookup --uid-validity 1 "INBOX"
+doveadm -u "local" mailbox update --no-userdb-lookup --uid-validity 2147483647 "foo" # 2^31-1
+doveadm -u "local" mailbox update --no-userdb-lookup --uid-validity 2147483648 "bar" # 2^31
+doveadm -u "local" mailbox update --no-userdb-lookup --uid-validity 4294967295 "baz" # 2^32-1
-doveadm -u "remote" mailbox update --uid-validity 4294967295 "INBOX" # 2^32-1
-doveadm -u "remote" mailbox update --uid-validity 2147483648 "foo" # 2^31
-doveadm -u "remote" mailbox update --uid-validity 2147483647 "bar" # 2^31-1
-doveadm -u "remote" mailbox update --uid-validity 1 "baz" #
+doveadm -u "remote" mailbox update --no-userdb-lookup --uid-validity 4294967295 "INBOX" # 2^32-1
+doveadm -u "remote" mailbox update --no-userdb-lookup --uid-validity 2147483648 "foo" # 2^31
+doveadm -u "remote" mailbox update --no-userdb-lookup --uid-validity 2147483647 "bar" # 2^31-1
+doveadm -u "remote" mailbox update --no-userdb-lookup --uid-validity 1 "baz" #
run() {
local u m i
@@ -25,15 +25,17 @@ run() {
run
# raise UIDNEXT AND HIGHESTMODSEQ close to the max values (resp. 2^32-1 och 2^63-1)
-doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "INBOX" # 2^31-1, 2^63-1
-doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "foo" # 2^31-1, 2^63-1
-doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63
-doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "baz" # 2^31, 2^63
+# XXX as of dovecot 2.4 --min-highest-modseq rejects values higher than INT64_MAX instead of UINT64_MAX,
+# cf. str_parse_int64()
+doveadm -u "local" mailbox update --no-userdb-lookup --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "INBOX" # 2^31-1, 2^63-1
+doveadm -u "local" mailbox update --no-userdb-lookup --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "foo" # 2^31-1, 2^63-1
+doveadm -u "local" mailbox update --no-userdb-lookup --min-next-uid 2147483648 --min-highest-modseq 9223372036854775807 "bar" # 2^31, 2^63-1
+doveadm -u "local" mailbox update --no-userdb-lookup --min-next-uid 2147483648 --min-highest-modseq 9223372036854775807 "baz" # 2^31, 2^63-1
-doveadm -u "remote" mailbox update --min-next-uid 4294967168 --min-highest-modseq 18446744073709551488 "INBOX" # 2^32-128, 2^64-128
-doveadm -u "remote" mailbox update --min-next-uid 2147483776 --min-highest-modseq 9223372036854775936 "foo" # 2^31+128, 2^63+128
-doveadm -u "remote" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63
+doveadm -u "remote" mailbox update --no-userdb-lookup --min-next-uid 4294967168 --min-highest-modseq 9223372036854775807 "INBOX" # 2^32-128, 2^63-1
+doveadm -u "remote" mailbox update --no-userdb-lookup --min-next-uid 2147483776 --min-highest-modseq 9223372036854775807 "foo" # 2^31+128, 2^63-1
+doveadm -u "remote" mailbox update --no-userdb-lookup --min-next-uid 2147483648 --min-highest-modseq 9223372036854775807 "bar" # 2^31, 2^63-1
run
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/list-mailbox/t b/tests/list-mailbox/t
index e905537..6c19705 100644
--- a/tests/list-mailbox/t
+++ b/tests/list-mailbox/t
@@ -1,6 +1,6 @@
# create and populate some mailboxes locally
declare -a MAILBOXES=( "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" )
-doveadm -u "local" mailbox create -- "${MAILBOXES[@]}" "foobad" "baz" "INBOX"
+doveadm -u "local" mailbox create --no-userdb-lookup -- "${MAILBOXES[@]}" "foobad" "baz" "INBOX"
for ((i = 0; i < 32; i++)); do
m="$(shuf -n1 -e -- "${MAILBOXES[@]}" "foobad" "baz" "INBOX")"
sample_message | deliver -u "local" -- -m "$m"
@@ -54,4 +54,4 @@ for v in '""' '"f o o""bar"' '"f o o" "bar" "baz\" x'; do
grep -xF "Invalid value for list-mailbox: $v" <"$STDERR"
done
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/list-reference/t b/tests/list-reference/t
index a2cc9c7..e8d38e6 100644
--- a/tests/list-reference/t
+++ b/tests/list-reference/t
@@ -1,6 +1,6 @@
# create and populate some mailboxes in and out the respective list references
-doveadm -u "local" mailbox create "foo" "foobar" "foo/bar/baz" "foo/baz" "bar" "bar/baz"
-doveadm -u "remote" mailbox create "foo" "foobaz" "foo/baz" "foo\\baz" "bar\\baz" "bar\\!"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo" "foobar" "foo/bar/baz" "foo/baz" "bar" "bar/baz"
+doveadm -u "remote" mailbox create --no-userdb-lookup "foo" "foobaz" "foo\\bar" "foo\\baz" "bar\\baz" "bar\\!"
populate() {
local i
@@ -8,7 +8,7 @@ populate() {
m="$(shuf -n1 -e -- "foo" "foobar" "foo/bar/baz" "foo/baz" "bar" "bar/baz")"
sample_message | deliver -u "local" -- -m "$m"
- m="$(shuf -n1 -e -- "foo" "foobar" "foo/baz" "foo\\baz" "bar\\baz" "bar\\!")"
+ m="$(shuf -n1 -e -- "foo" "foobaz" "foo\\bar" "foo\\baz" "bar\\baz" "bar\\!")"
sample_message | deliver -u "remote" -- -m "$m"
done
}
@@ -44,4 +44,4 @@ verify
! doveadm -u "local" mailbox status uidvalidity "foobaz" || error
! doveadm -u "remote" mailbox status uidvalidity "foobar" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/list-select-opts/t b/tests/list-select-opts/t
index 98acb43..570098c 100644
--- a/tests/list-select-opts/t
+++ b/tests/list-select-opts/t
@@ -1,5 +1,5 @@
-doveadm -u "local" mailbox create -s "INBOX" "foo.bar"
-doveadm -u "remote" mailbox create -s "INBOX" "bar"
+doveadm -u "local" mailbox create --no-userdb-lookup -s "INBOX" "foo.bar"
+doveadm -u "remote" mailbox create --no-userdb-lookup -s "INBOX" "bar"
interimap_init
check_mailbox_list
@@ -15,8 +15,8 @@ for ((i = 0; i < 16; i++)); do
done
# create new unsubscribed mailboxes
-doveadm -u "local" mailbox create "foo"
-doveadm -u "remote" mailbox create "baz"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo"
+doveadm -u "remote" mailbox create --no-userdb-lookup "baz"
for ((i = 0; i < 8; i++)); do
u="$(shuf -n1 -e "local" "remote")" # choose target at random
@@ -37,12 +37,12 @@ diff -u --label="a/mailboxes.sql" --label="b/mailboxes.sql" \
check_mailboxes_status "INBOX" "foo.bar" "bar"
# double check the unsubscribed mailboxes weren't copied
-! doveadm -u "remote" mailbox status uidvalidity "foo" || error
-! doveadm -u "local" mailbox status uidvalidity "baz" || error
+! doveadm -u "remote" mailbox status --no-userdb-lookup uidvalidity "foo" || error
+! doveadm -u "local" mailbox status --no-userdb-lookup uidvalidity "baz" || error
# reconcile when subcribed
-doveadm -u "local" mailbox subscribe "foo"
-doveadm -u "remote" mailbox subscribe "baz"
+doveadm -u "local" mailbox subscribe --no-userdb-lookup "foo"
+doveadm -u "remote" mailbox subscribe --no-userdb-lookup "baz"
interimap || error
grep -Fx "database: Created mailbox foo" <"$STDERR" || error
@@ -53,4 +53,4 @@ grep -Fx "remote: Created mailbox foo" <"$STDERR" || error
check_mailbox_list
check_mailboxes_status "INBOX" "foo" "foo.bar" "bar" "baz"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/preauth-plaintext/imapd b/tests/preauth-plaintext/imapd
new file mode 100755
index 0000000..bf2ed72
--- /dev/null
+++ b/tests/preauth-plaintext/imapd
@@ -0,0 +1,44 @@
+#!/usr/bin/perl -T
+
+use warnings;
+use strict;
+
+use Errno qw/EINTR/;
+use Socket qw/INADDR_LOOPBACK AF_INET SOCK_STREAM pack_sockaddr_in
+ SOL_SOCKET SO_REUSEADDR SHUT_RDWR/;
+
+socket(my $S, AF_INET, SOCK_STREAM, 0) or die;
+setsockopt($S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
+bind($S, pack_sockaddr_in(10143, INADDR_LOOPBACK)) or die "bind: $!\n";
+listen($S, 1) or die "listen: $!";
+
+while (1) {
+ my $sockaddr = accept(my $conn, $S) or do {
+ next if $! == EINTR;
+ die "accept: $!";
+ };
+
+ # minimum CAPABILITY list, see tests/config/dovecot/interimap-required-capabilities.conf
+ $conn->printflush("* PREAUTH [CAPABILITY IMAP4rev1 ENABLE UIDPLUS LIST-EXTENDED QRESYNC LIST-STATUS] IMAP4rev1 Server\r\n");
+ my $x;
+
+ $x = $conn->getline() // next;
+ $x =~ /\A(\S+) ENABLE QRESYNC\r\n/ or die;
+ $conn->printflush("* ENABLED QRESYNC\r\n$1 OK ENABLE completed\r\n");
+
+ $x = $conn->getline() // next;
+ $x =~ /\A(\S+) LIST .*\r\n/ or die;
+ $conn->print("* LIST (\\Noselect) \"~\" \"\"\r\n");
+ $conn->print("* LIST () \"~\" INBOX\r\n");
+ $conn->print("* STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1 HIGHESTMODSEQ 1)\r\n");
+ $conn->printflush("$1 OK LIST completed\r\n");
+
+ close($conn);
+}
+
+END {
+ if (defined $S) {
+ shutdown($S, SHUT_RDWR) or warn "shutdown: $!";
+ close($S) or print STDERR "close: $!\n";
+ }
+}
diff --git a/tests/preauth-plaintext/interimap.remote b/tests/preauth-plaintext/interimap.remote
new file mode 120000
index 0000000..ad49677
--- /dev/null
+++ b/tests/preauth-plaintext/interimap.remote
@@ -0,0 +1 @@
+../starttls/interimap.remote \ No newline at end of file
diff --git a/tests/preauth-plaintext/t b/tests/preauth-plaintext/t
new file mode 100644
index 0000000..2f3071f
--- /dev/null
+++ b/tests/preauth-plaintext/t
@@ -0,0 +1,19 @@
+# Test IMAP MiTM via PREAUTH greeting
+# For background see CVE-2020-12398, CVE-2020-14093 and
+# https://gitlab.com/muttmua/mutt/commit/3e88866dc60b5fa6aaba6fd7c1710c12c1c3cd01
+
+env -i USER="remote" HOME="$HOME_remote" "$TESTDIR/imapd" & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+
+! interimap --debug || error
+grep -Fx 'remote: ERROR: PREAUTH greeting on plaintext connection? MiTM in action? Aborting, set "STARTTLS = NO" to ignore.' <"$STDERR" || error
+! grep '^remote: C: ' <"$STDERR" || error "wrote command in MiTM'ed PREAUTH connection!"
+
+
+# Ignore the warning when STARTTLS is explicitly disabled
+echo "STARTTLS = NO" >>"$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || true
+
+grep -Fx "remote: S: * STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1 HIGHESTMODSEQ 1)" <"$STDERR" || error
+
+# vim: set filetype=bash :
diff --git a/tests/pullimap.list b/tests/pullimap.list
new file mode 100644
index 0000000..f4304b9
--- /dev/null
+++ b/tests/pullimap.list
@@ -0,0 +1,2 @@
+. pullimap
+ ... pullimap
diff --git a/tests/pullimap/t b/tests/pullimap/t
index 115432d..1c57f8d 100644
--- a/tests/pullimap/t
+++ b/tests/pullimap/t
@@ -6,16 +6,32 @@ step_start "\`pullimap --idle\` refuses to create the state file"
! pullimap --idle "remote" || error
step_done
-# compare mailboxes; can't compare the RFC 3501 TEXT as LMTP adds a
-# Received: header.
-# TODO unset lmtp_add_received_header once available in sid:
-# https://doc.dovecot.org/settings/dovecot_core_settings/#lmtp-add-received-header
+step_start "\`pullimap\` creates statefile with mode 0600"
+pullimap "remote" || error
+if ! st="$(stat -c"%#a" -- "$XDG_DATA_HOME/pullimap/remote")" || [ "$st" != "0600" ]; then
+ error "$XDG_DATA_HOME/pullimap/remote has mode $st != 0600"
+fi
+step_done
+
+step_start "\`pullimap\` locks its statefile"
+pullimap --idle "remote" & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+# wait a short while so we have time to lock the database (ugly and racy...)
+sleep .5
+! pullimap "remote" || error
+grep -F "Can't lock $XDG_DATA_HOME/pullimap/remote: Resource temporarily unavailable at " <"$STDERR" || error
+ptree_abort $PID
+trap - EXIT INT TERM
+step_done
+
+# compare mailboxes (can't compare the RFC 3501 TEXT as the LMTPd inconditionally
+# adds a Return-Path: header -- and also Delivered-To: and Received: to by default)
list_mails_sha256() {
local u="$1" guid uid
+ local fields="body date.sent imap.bodystructure imap.envelope"
while read guid uid; do
- doveadm -u "$u" -f "flow" fetch body mailbox-guid "$guid" uid "$uid" \
- | sed "1s/body=//" | sha256sum
- done < <(doveadm -u "$u" search mailbox "$MAILBOX") | sort -f
+ doveadm -u "$u" -f "flow" fetch --no-userdb-lookup "$fields" mailbox-guid "$guid" uid "$uid" | sha256sum
+ done < <(doveadm -u "$u" search mailbox --no-userdb-lookup "$MAILBOX") | sort -f
}
check() {
diff -u --label="local/mails" --label="remote/mails" \
@@ -75,7 +91,7 @@ step_done
# make sure remote UIDs are 11-bytes long
-doveadm -u "remote" mailbox update --min-next-uid 1000000000 "$MAILBOX"
+doveadm -u "remote" mailbox update --no-userdb-lookup --min-next-uid 1000000000 "$MAILBOX"
# Add some messages and sync
step_start "Fetching messages"
@@ -93,7 +109,7 @@ for ((i = 0; i < N; i+=2)); do
done
for ((i = 0; i < N; i+=2)); do
# expunge every other message
- doveadm -u "remote" expunge mailbox "$MAILBOX" $((N-i+32+7))
+ doveadm -u "remote" expunge --no-userdb-lookup mailbox "$MAILBOX" $((N-i+32+7))
sample_message | deliver -u "remote" -- -m "$MAILBOX"
done
@@ -101,34 +117,36 @@ pullimap "remote" || error
check
# count unseen remote messages
-doveadm -u "remote" search mailbox "$MAILBOX" unseen >"$TMPDIR/unseen"
+doveadm -u "remote" search --no-userdb-lookup mailbox "$MAILBOX" unseen >"$TMPDIR/unseen"
[ ! -s "$TMPDIR/unseen" ] || error "\\Unseen messages left"
step_done
-step_start "--idle (${TIMEOUT}s)"
+if [ $TIMEOUT -gt 0 ]; then
+ step_start "--idle (${TIMEOUT}s)"
-pullimap --idle "remote" & PID=$!
-trap "ptree_abort $PID" EXIT INT TERM
+ pullimap --idle "remote" & PID=$!
+ trap "ptree_abort $PID" EXIT INT TERM
+
+ timer=$(( $(date +%s) + TIMEOUT ))
+ while [ $(date +%s) -le $timer ]; do
+ n="$(shuf -n1 -i1-5)"
+ for (( i=0; i < n; i++)); do
+ sample_message | deliver -u "remote" -- -m "$MAILBOX"
+ done
-timer=$(( $(date +%s) + TIMEOUT ))
-while [ $(date +%s) -le $timer ]; do
- n="$(shuf -n1 -i1-5)"
- for (( i=0; i < n; i++)); do
- sample_message | deliver -u "remote" -- -m "$MAILBOX"
+ s=$(shuf -n1 -i1-1500)
+ [ $s -ge 1000 ] && s="$(printf "1.%03d" $((s-1000)))" || s="$(printf "0.%03d" $s)"
+ sleep "$s"
done
- s=$(shuf -n1 -i1-1500)
- [ $s -ge 1000 ] && s="$(printf "1.%03d" $((s-1000)))" || s="$(printf "0.%03d" $s)"
- sleep "$s"
-done
+ sleep 5
+ ptree_abort $PID
+ trap - EXIT INT TERM
-sleep 5
-ptree_abort $PID
-trap - EXIT INT TERM
-
-check
-step_done
+ check
+ step_done
+fi
step_start "Purging"
@@ -138,8 +156,8 @@ for ((i = 0; i < 32; i++)); do
done
pullimap "remote"
-doveadm -u "remote" search mailbox "$MAILBOX" all >"$TMPDIR/messages"
+doveadm -u "remote" search --no-userdb-lookup mailbox "$MAILBOX" all >"$TMPDIR/messages"
[ ! -s "$TMPDIR/messages" ] || error "messages left"
step_done
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/rename-exists-db/t b/tests/rename-exists-db/t
index cb6cfcd..2ee8d77 100644
--- a/tests/rename-exists-db/t
+++ b/tests/rename-exists-db/t
@@ -1,14 +1,14 @@
-doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o"
-doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" "t\\o"
+doveadm -u "local" mailbox create --no-userdb-lookup "root.from" "root.from.child" "t.o"
+doveadm -u "remote" mailbox create --no-userdb-lookup "root\\from" "root\\from\\child" "t\\o"
interimap_init
check_mailbox_list
# delete a mailbox on both servers but leave it in the database, then try to use it as target for --rename
-doveadm -u "local" mailbox delete "t.o"
-doveadm -u "remote" mailbox delete "t\\o"
+doveadm -u "local" mailbox delete --no-userdb-lookup "t.o"
+doveadm -u "remote" mailbox delete --no-userdb-lookup "t\\o"
! interimap --rename "root.from" "t.o" || error
grep -Fx 'database: ERROR: Mailbox t.o exists. Run `interimap --target=database --delete t.o` to delete.' <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/rename-exists-local/t b/tests/rename-exists-local/t
index 190f49a..907b14a 100644
--- a/tests/rename-exists-local/t
+++ b/tests/rename-exists-local/t
@@ -1,13 +1,13 @@
-doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o"
-doveadm -u "remote" mailbox create "root\\from" "root\\from\\child"
+doveadm -u "local" mailbox create --no-userdb-lookup "root.from" "root.from.child" "t.o"
+doveadm -u "remote" mailbox create --no-userdb-lookup "root\\from" "root\\from\\child"
interimap_init
check_mailbox_list
# delete a mailbox on the remote server, then try to use it as target for --rename
-doveadm -u "remote" mailbox delete "t\\o"
+doveadm -u "remote" mailbox delete --no-userdb-lookup "t\\o"
! interimap --rename "root.from" "t.o" || error
grep -Fx 'local: ERROR: Mailbox t.o exists. Run `interimap --target=local --delete t.o` to delete.' <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/rename-exists-remote/t b/tests/rename-exists-remote/t
index be16a12..5cb4336 100644
--- a/tests/rename-exists-remote/t
+++ b/tests/rename-exists-remote/t
@@ -1,13 +1,13 @@
-doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o"
-doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" "t\\o"
+doveadm -u "local" mailbox create --no-userdb-lookup "root.from" "root.from.child" "t.o"
+doveadm -u "remote" mailbox create --no-userdb-lookup "root\\from" "root\\from\\child" "t\\o"
interimap_init
check_mailbox_list
# delete a mailbox on the local server, then try to use it as target for --rename
-doveadm -u "local" mailbox delete "t.o"
+doveadm -u "local" mailbox delete --no-userdb-lookup "t.o"
! interimap --rename "root.from" "t.o" || error
grep -Fx 'remote: ERROR: Mailbox t\o exists. Run `interimap --target=remote --delete t.o` to delete.' <"$STDERR" || remote
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/rename-inferiors/t b/tests/rename-inferiors/t
index 9267e6f..3715680 100644
--- a/tests/rename-inferiors/t
+++ b/tests/rename-inferiors/t
@@ -1,5 +1,5 @@
-doveadm -u "local" mailbox create "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild"
-doveadm -u "remote" mailbox create "root^sibbling" "root^sibbling^grandchild" "root2"
+doveadm -u "local" mailbox create --no-userdb-lookup "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild"
+doveadm -u "remote" mailbox create --no-userdb-lookup "root^sibbling" "root^sibbling^grandchild" "root2"
for m in "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" "INBOX"; do
sample_message | deliver -u "local" -- -m "$m"
@@ -97,4 +97,4 @@ check_mailbox_list
check_mailboxes_status "from.root" "from.root.child" "from.root.child2" "from.root.child.grandchild" \
"newroot.sibbling" "newroot.sibbling.grandchild" "root2" "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/rename-simple/t b/tests/rename-simple/t
index 6ebee9a..82682fb 100644
--- a/tests/rename-simple/t
+++ b/tests/rename-simple/t
@@ -1,4 +1,4 @@
-doveadm -u "local" mailbox create "foo"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo"
sample_message | deliver -u "local" -- -m "INBOX"
sample_message | deliver -u "remote" -- -m "INBOX"
@@ -58,4 +58,4 @@ grep -Fx "database: Created mailbox INBOX" <"$STDERR"
check_mailbox_list
check_mailboxes_status "INBOX" "bar" "baz"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/repair/t b/tests/repair/t
index 6b205ea..bdad2e3 100644
--- a/tests/repair/t
+++ b/tests/repair/t
@@ -1,6 +1,6 @@
# create some mailboxes and populate them
-doveadm -u "local" mailbox create "foo.bar"
-doveadm -u "remote" mailbox create "foo~bar" "baz"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo.bar"
+doveadm -u "remote" mailbox create --no-userdb-lookup "foo~bar" "baz"
for ((i = 0; i < 8; i++)); do
sample_message | deliver -u "local" -- -m "foo.bar"
sample_message | deliver -u "remote" -- -m "foo~bar"
@@ -15,12 +15,12 @@ check_mailboxes_status "foo.bar" "baz" "INBOX"
# make more changes (flag updates, new massages, deletions)
sample_message | deliver -u "remote" -- -m "INBOX"
-doveadm -u "local" expunge mailbox "baz" 1:10
-doveadm -u "remote" expunge mailbox "baz" "$(seq -s"," 1 2 32),$(seq -s"," 40 2 64)"
-doveadm -u "local" expunge mailbox "foo.bar" 2,3,5:7,10
-doveadm -u "remote" expunge mailbox "foo~bar" 4,5,7,10
-doveadm -u "local" flags add "\\Answered" mailbox "foo.bar" 2,3,5:7,10
-doveadm -u "remote" flags add "\\Seen" mailbox "foo~bar" 4,5,7
+doveadm -u "local" expunge --no-userdb-lookup mailbox "baz" 1:10
+doveadm -u "remote" expunge --no-userdb-lookup mailbox "baz" "$(seq -s"," 1 2 32),$(seq -s"," 40 2 64)"
+doveadm -u "local" expunge --no-userdb-lookup mailbox "foo.bar" 2,3,5:7,10
+doveadm -u "remote" expunge --no-userdb-lookup mailbox "foo~bar" 4,5,7,10
+doveadm -u "local" flags add --no-userdb-lookup "\\Answered" mailbox "foo.bar" 2,3,5:7,10
+doveadm -u "remote" flags add --no-userdb-lookup "\\Seen" mailbox "foo~bar" 4,5,7
# spoof HIGHESTMODSEQ value in the database to make it look that we recorded the new changes already
spoof() {
@@ -28,7 +28,7 @@ spoof() {
shift
while [ $# -gt 0 ]; do
[ "$1" = "local" ] && m="foo.bar" || m="$(printf "%s" "foo.bar" | tr "." "~")"
- v="$(doveadm -u "$1" -f flow mailbox status "${k,,[A-Z]}" "$m" | sed 's/.*=//')"
+ v="$(doveadm -u "$1" -f flow mailbox status --no-userdb-lookup "${k,,[A-Z]}" "$m" | sed 's/.*=//')"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF
UPDATE \`$1\` SET $k = $v
WHERE idx = (SELECT idx FROM mailboxes WHERE mailbox = x'$hex');
@@ -41,8 +41,8 @@ spoof HIGHESTMODSEQ "local" "remote"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF
.dump
EOF
-doveadm -u "local" mailbox status "all" "foo.bar" >"$TMPDIR/foo-bar.status.local"
-doveadm -u "remote" mailbox status "all" "foo~bar" >"$TMPDIR/foo-bar.status.remote"
+doveadm -u "local" mailbox status --no-userdb-lookup "all" "foo.bar" >"$TMPDIR/foo-bar.status.local"
+doveadm -u "remote" mailbox status --no-userdb-lookup "all" "foo~bar" >"$TMPDIR/foo-bar.status.remote"
# verify that without --repair interimap does nothing due to the spoofed HIGHESTMODSEQ values
@@ -51,8 +51,8 @@ interimap "foo.bar" || error
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF
.dump
EOF
-doveadm -u "local" mailbox status all "foo.bar" >"$TMPDIR/foo-bar.status2.local"
-doveadm -u "remote" mailbox status all "foo~bar" >"$TMPDIR/foo-bar.status2.remote"
+doveadm -u "local" mailbox status --no-userdb-lookup all "foo.bar" >"$TMPDIR/foo-bar.status2.local"
+doveadm -u "remote" mailbox status --no-userdb-lookup all "foo~bar" >"$TMPDIR/foo-bar.status2.remote"
diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql"
diff -u --label="a/foo_bar.local" --label="a/foo_bar.local" "$TMPDIR/foo-bar.status.local" "$TMPDIR/foo-bar.status2.local"
diff -u --label="a/foo_bar.remote" --label="a/foo_bar.remote" "$TMPDIR/foo-bar.status.remote" "$TMPDIR/foo-bar.status2.remote"
@@ -104,4 +104,4 @@ check_mailboxes_status "baz" "foo.bar"
interimap || error
check_mailboxes_status "baz" "foo.bar" "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/resume/t b/tests/resume/t
index a281ef3..da80557 100644
--- a/tests/resume/t
+++ b/tests/resume/t
@@ -1,5 +1,5 @@
# create and populate a bunch of mailboxes
-doveadm -u "local" mailbox create "foo" "foo.bar" "baz"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo" "foo.bar" "baz"
for ((i = 0; i < 8; i++)); do
sample_message | deliver -u "local" -- -m "foo"
sample_message | deliver -u "local" -- -m "foo.bar"
@@ -24,17 +24,17 @@ set_uidnext() {
}
# spoof "foo"'s UIDVALIDITY and UIDNEXT values
-uidvalidity="$(doveadm -u "local" -f flow mailbox status uidvalidity "foo" | sed 's/.*=//')"
+uidvalidity="$(doveadm -u "local" -f flow mailbox status --no-userdb-lookup uidvalidity "foo" | sed 's/.*=//')"
[ $uidvalidity -eq 4294967295 ] && uidvalidity2=1 || uidvalidity2=$((uidvalidity+1))
-doveadm -u "local" mailbox update --uid-validity "$uidvalidity2" "foo"
+doveadm -u "local" mailbox update --no-userdb-lookup --uid-validity "$uidvalidity2" "foo"
set_uidnext "local" "$(printf "%s" "foo" | xxd -ps)" 1
# verify that interimap chokes on the UIDVALIDITY change without doing any changes
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF
.dump
EOF
-doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local"
-doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote"
+doveadm -u "local" mailbox status --no-userdb-lookup "all" "foo" >"$TMPDIR/foo.local"
+doveadm -u "remote" mailbox status --no-userdb-lookup "all" "foo" >"$TMPDIR/foo.remote"
! interimap || error
grep -Fx "Resuming interrupted sync for foo" <"$STDERR"
@@ -43,8 +43,8 @@ grep -Fx "local(foo): ERROR: UIDVALIDITY changed! ($uidvalidity2 != $uidvalidity
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF
.dump
EOF
-doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local2"
-doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote2"
+doveadm -u "local" mailbox status --no-userdb-lookup "all" "foo" >"$TMPDIR/foo.local2"
+doveadm -u "remote" mailbox status --no-userdb-lookup "all" "foo" >"$TMPDIR/foo.remote2"
diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump2.sql" "$TMPDIR/dump.sql"
diff -u --label="a/foo.local" --label="b/foo.remote" "$TMPDIR/foo.local" "$TMPDIR/foo.local2"
@@ -57,12 +57,12 @@ set_uidnext "remote" "$(printf "%s" "INBOX" | xxd -ps)" 2
set_uidnext "remote" "$(printf "%s\\0%s" "foo" "bar" | xxd -ps)" 0
# set some flags and remove some messages for UIDs >2
-doveadm -u "local" flags add "\\Seen" mailbox "INBOX" 6,7
-doveadm -u "remote" flags add "\\Deleted" mailbox "INBOX" 6,8
+doveadm -u "local" flags add --no-userdb-lookup "\\Seen" mailbox "INBOX" 6,7
+doveadm -u "remote" flags add --no-userdb-lookup "\\Deleted" mailbox "INBOX" 6,8
-doveadm -u "local" expunge mailbox "INBOX" 4,5
-doveadm -u "remote" expunge mailbox "INBOX" 3,4
-doveadm -u "remote" expunge mailbox "foo~bar" 5
+doveadm -u "local" expunge --no-userdb-lookup mailbox "INBOX" 4,5
+doveadm -u "remote" expunge --no-userdb-lookup mailbox "INBOX" 3,4
+doveadm -u "remote" expunge --no-userdb-lookup mailbox "foo~bar" 5
# add new messages
sample_message | deliver -u "local" -- -m "foo.bar"
@@ -95,4 +95,4 @@ diff -u --label="a/count" --label="b/count" "$TMPDIR/count" - <<-EOF
9
EOF
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/run b/tests/run
index a34e4a9..c5614ee 100755
--- a/tests/run
+++ b/tests/run
@@ -15,7 +15,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
#----------------------------------------------------------------------
set -ue
@@ -35,6 +35,15 @@ if [ ! -d "$TESTDIR" ]; then
exit 1
fi
+# cleanup environment
+unset OPENSSL_CONF SSL_CERT_FILE SSL_CERT_DIR
+
+if [ -z "${INTERIMAP_PATH+x}" ]; then
+ INTERIMAP_PATH="./"
+elif [ -n "$INTERIMAP_PATH" ]; then
+ INTERIMAP_PATH="${INTERIMAP_PATH%/}/"
+fi
+
ROOTDIR="$(mktemp --tmpdir="${TMPDIR:-/dev/shm}" --directory "$1.XXXXXXXXXX")"
declare -a DOVECOT_SERVER=()
trap cleanup EXIT INT TERM
@@ -88,12 +97,15 @@ prepare() {
mkdir -pm0700 -- "$home/.dovecot"
cat >"$home/.dovecot/config" <<-EOF
+ dovecot_config_version = 2.4.0
+ dovecot_storage_version = 2.4.0
log_path = $HOME_local/mail.log
mail_home = $home
- mail_location = dbox:~/inbox:LAYOUT=index
+ mail_driver = sdbox
+ mail_path = ~/inbox
mailbox_list_index = yes
ssl = no
- listen = 127.0.0.1, ::1
+ listen = 127.0.0.1, 127.0.1.1, ::1
namespace inbox {
inbox = yes
}
@@ -101,12 +113,15 @@ prepare() {
if [ -f "$TESTDIR/$u.conf" ] || [ -L "$TESTDIR/$u.conf" ]; then
cat >>"$home/.dovecot/config" <"$TESTDIR/$u.conf"
fi
- cp -aT -- "$BASEDIR/snippets/dovecot" "$home/.dovecot/conf.d"
+ cp -aT -- "$BASEDIR/config/dovecot" "$home/.dovecot/conf.d"
+ cp -at "$home/.dovecot/conf.d" -- "$BASEDIR/certs/ca.crt" "$BASEDIR/certs"/dovecot.*
proto="$(env -i "${ENVIRON[@]}" doveconf -c "$home/.dovecot/config" -h protocols)"
if [ -n "$proto" ]; then
cat >>"$home/.dovecot/config" <<-EOF
- # https://wiki.dovecot.org/HowTo/Rootless
+ # https://doc.dovecot.org/latest/core/config/rootless.html#rootless-installation
+ dovecot_config_version = 2.4.0
+ dovecot_storage_version = 2.4.0
base_dir = $home/.dovecot/run
default_internal_user = $(id -un)
default_internal_group = $(id -gn)
@@ -122,13 +137,14 @@ prepare() {
chroot =
}
- passdb {
- args = scheme=PLAIN username_format=%u $home/.dovecot/users
+ passdb passwd-file {
driver = passwd-file
+ default_password_scheme = plain
+ passwd_file_path = $home/.dovecot/users
}
- userdb {
- args = username_format=%u $home/.dovecot/users
+ userdb passwd-file {
driver = passwd-file
+ passwd_file_path = $home/.dovecot/users
}
EOF
@@ -202,21 +218,32 @@ prepare
interimap() { _interimap_cmd "interimap" "$@"; }
pullimap() { _interimap_cmd "pullimap" "$@"; }
_interimap_cmd() {
- declare -a ENVIRON=() r=0
- local script="$1"
+ declare -a ENVIRON=() args=()
+ local script="$1" rv=0
shift
environ_set "local"
- env -i "${ENVIRON[@]}" perl -I./lib -T "./$script" "$@" 2> >(tee "$STDERR" >&2)
+ [ -z "${OPENSSL_CONF+x}" ] || ENVIRON+=( OPENSSL_CONF="$OPENSSL_CONF" )
+ [ -z "${SSL_CERT_FILE+x}" ] || ENVIRON+=( SSL_CERT_FILE="$SSL_CERT_FILE" )
+ [ -z "${SSL_CERT_DIR+x}" ] || ENVIRON+=( SSL_CERT_DIR="$SSL_CERT_DIR" )
+ [ -z "${INTERIMAP_I:+x}" ] || args+=( perl -I"$INTERIMAP_I" -T )
+ args+=( "$INTERIMAP_PATH$script" "$@" )
+ #printf "I: Running \`%s\`\\n" "${args[*]}" >&3
+ env -i "${ENVIRON[@]}" "${args[@]}" 2>"$STDERR" || rv=$?
+ cat <"$STDERR" >&2
+ return $rv
}
interimap_init() {
local u="${1-remote}"
- local db="$XDG_DATA_HOME/interimap/$u.db"
+ local db="$XDG_DATA_HOME/interimap/$u.db" st
local cfg="config${u#remote}"
test \! -e "$db" || error "Database already exists" 1
interimap --config "$cfg" || error "Couldn't initialize interimap" 1
test -f "$db" || error "Database is still missing" 1
grep -Fx "Creating new schema in database file $db" <"$STDERR" || error "DB wasn't created" 1
+ if ! st="$(stat -c"%#a" -- "$db")" || [ "$st" != "0600" ]; then
+ error "$db has mode $st != 0600" 1
+ fi
}
doveadm() {
if [ $# -le 2 ] || [ "$1" != "-u" ]; then
@@ -331,9 +358,9 @@ check_mailbox_status2() {
check_mailbox_status_values "$u" "$rmailbox" $rUIDVALIDITY $rUIDNEXT $rHIGHESTMODSEQ $MESSAGES
local a b
- a="$(doveadm -u "local" -f "flow" mailbox status "messages unseen vsize" -- "$lmailbox" | \
+ a="$(doveadm -u "local" -f "flow" mailbox status --no-userdb-lookup "messages unseen vsize" -- "$lmailbox" | \
sed -nr '/.*\s+(\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+)$/ {s//\1/p;q}')"
- b="$(doveadm -u "$u" -f "flow" mailbox status "messages unseen vsize" -- "$rmailbox" | \
+ b="$(doveadm -u "$u" -f "flow" mailbox status --no-userdb-lookup "messages unseen vsize" -- "$rmailbox" | \
sed -nr '/.*\s+(\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+)$/ {s//\1/p;q}')"
if [ "$a" != "$b" ]; then
echo "Mailbox $lmailbox status differs: \"$a\" != \"$b\"" >&2
@@ -342,7 +369,7 @@ check_mailbox_status2() {
}
check_mailbox_status_values() {
local user="$1" mailbox="$2" UIDVALIDITY="$3" UIDNEXT="$4" HIGHESTMODSEQ="$5" MESSAGES="$6" x xs v k
- xs="$(doveadm -u "$user" -f "flow" mailbox status "uidvalidity uidnext highestmodseq messages" -- "$mailbox" | \
+ xs="$(doveadm -u "$user" -f "flow" mailbox status --no-userdb-lookup "uidvalidity uidnext highestmodseq messages" -- "$mailbox" | \
sed -nr '/.*\s+(\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+)$/ {s//\1/p;q}')"
[ -n "$xs" ] || exit 1
for x in $xs; do
@@ -390,12 +417,12 @@ check_mailbox_list() {
done
fi
- mapfile -t lmailboxes < <( doveadm -u "local" mailbox list $sub -- "${lmailboxes[@]}" )
+ mapfile -t lmailboxes < <( doveadm -u "local" mailbox list --no-userdb-lookup $sub -- "${lmailboxes[@]}" )
for ((i = 0; i < ${#lmailboxes[@]}; i++)); do
lmailboxes[i]="${lmailboxes[i]#"$lprefix"}"
done
- mapfile -t rmailboxes < <( doveadm -u "remote" mailbox list $sub -- "${rmailboxes[@]}" )
+ mapfile -t rmailboxes < <( doveadm -u "remote" mailbox list --no-userdb-lookup $sub -- "${rmailboxes[@]}" )
for ((i = 0; i < ${#rmailboxes[@]}; i++)); do
rmailboxes[i]="${rmailboxes[i]#"$rprefix"}"
rmailboxes[i]="${rmailboxes[i]//"$rsep"/"$lsep"}"
@@ -440,7 +467,7 @@ passed() {
# Run test in a sub-shell
declare -a ENVIRON=()
environ_set "local"
-export TMPDIR TESTDIR STDERR "${ENVIRON[@]}"
+export TMPDIR TESTDIR INTERIMAP_PATH INTERIMAP_I STDERR "${ENVIRON[@]}"
export -f environ_set doveadm interimap interimap_init pullimap _interimap_cmd
export -f sqlite3 sample_message deliver ptree_abort step_start step_done passed
export -f check_mailbox_status check_mailbox_status_values check_mailbox_status2
diff --git a/tests/run-all b/tests/run-all
index 1eca50c..79e62d1 100755
--- a/tests/run-all
+++ b/tests/run-all
@@ -15,7 +15,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
#----------------------------------------------------------------------
set -ue
@@ -24,6 +24,7 @@ export PATH
BASEDIR="$(dirname -- "$0")"
RUN="$BASEDIR/run"
+list="$1"
failed=0
@@ -54,7 +55,7 @@ while IFS="" read -r x; do
fi
INDENT="$indent" "$RUN" "$t" ${desc+"$desc"} || failed=$(( failed+1 ))
-done <"$BASEDIR/list"
+done <"$BASEDIR/$list"
if [ $failed -eq 0 ]; then
printf "All tests passed.\\n"
diff --git a/tests/snippets/dovecot/dovecot.key b/tests/snippets/dovecot/dovecot.key
deleted file mode 100644
index 95c9846..0000000
--- a/tests/snippets/dovecot/dovecot.key
+++ /dev/null
@@ -1,5 +0,0 @@
------BEGIN EC PRIVATE KEY-----
-MHcCAQEEIGkqkKq69zVeF17S3y2U2HkQWh8z9M/xeblCztkKIfzJoAoGCCqGSM49
-AwEHoUQDQgAE1LLppulKw8KjINrDhOjEd0NTax5iDCds+vpA2PwsvvtGoprNAjQM
-zX+40u30N3CE0r591txqohSBQ/X+nvG2ug==
------END EC PRIVATE KEY-----
diff --git a/tests/snippets/dovecot/dovecot.pem b/tests/snippets/dovecot/dovecot.pem
deleted file mode 100644
index 7e53d90..0000000
--- a/tests/snippets/dovecot/dovecot.pem
+++ /dev/null
@@ -1,11 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIBkzCCATmgAwIBAgIUQ+3hBMsPJcl59xDDujDDfexurOswCgYIKoZIzj0EAwIw
-HzEdMBsGA1UEAwwUSW50ZXJJTUFQIHRlc3Qgc3VpdGUwHhcNMTkxMTEwMTM1NDAw
-WhcNMjkxMTA3MTM1NDAwWjAfMR0wGwYDVQQDDBRJbnRlcklNQVAgdGVzdCBzdWl0
-ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNSy6abpSsPCoyDaw4ToxHdDU2se
-YgwnbPr6QNj8LL77RqKazQI0DM1/uNLt9DdwhNK+fdbcaqIUgUP1/p7xtrqjUzBR
-MB0GA1UdDgQWBBRlh8nSwyX+VlhwuhV7RKYwvKLyDzAfBgNVHSMEGDAWgBRlh8nS
-wyX+VlhwuhV7RKYwvKLyDzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA
-MEUCIQDK8xPPHTbYW5JnZ1Siy8ChZ6GOu2sRwQu7OgtGYGZRSQIgFKn1oAhnq2Oi
-aIPqxjvBPMsK/sjrdI/rNsr2XgaulU4=
------END CERTIFICATE-----
diff --git a/tests/snippets/dovecot/ssl.conf b/tests/snippets/dovecot/ssl.conf
deleted file mode 100644
index 240f24b..0000000
--- a/tests/snippets/dovecot/ssl.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-ssl = required
-ssl_cert = <dovecot.pem
-ssl_key = <dovecot.key
-ssl_dh = <dhparams.pem
diff --git a/tests/split-set/t b/tests/split-set/t
index 5e8ea52..7e124e3 100644
--- a/tests/split-set/t
+++ b/tests/split-set/t
@@ -7,8 +7,8 @@ N=2048
# set UIDNEXT to 10^9 so all uids are 10 chars long, otherwise we'd need
# to add many more messages to obtain large sets
-doveadm -u "local" mailbox update --min-next-uid 1000000000 "INBOX"
-doveadm -u "remote" mailbox update --min-next-uid 1000000000 "INBOX"
+doveadm -u "local" mailbox update --no-userdb-lookup --min-next-uid 1000000000 "INBOX"
+doveadm -u "remote" mailbox update --no-userdb-lookup --min-next-uid 1000000000 "INBOX"
for ((i = 0; i < N; i++)); do
u="$(shuf -n1 -e "local" "remote")"
@@ -20,7 +20,7 @@ check_mailbox_status "INBOX"
# mark every other message as \Seen on the local server
for ((i = 0; i < N; i+=2)); do
- doveadm -u "local" flags add "\\Seen" mailbox "INBOX" $((N-i))
+ doveadm -u "local" flags add --no-userdb-lookup "\\Seen" mailbox "INBOX" $((N-i))
done
# send the changes to the remote; this results into an UID STORE set
@@ -31,7 +31,7 @@ check_mailbox_status "INBOX"
# now expunge every other message on the remote server; this results
# into large UID STORE and UID EXPUNGE set representation
for ((i = 0; i < N; i+=2)); do
- doveadm -u "local" expunge mailbox "INBOX" $((N-i))
+ doveadm -u "local" expunge --no-userdb-lookup mailbox "INBOX" $((N-i))
# add some more messages
u="$(shuf -n1 -e "local" "remote")"
sample_message | deliver -u "$u"
@@ -40,4 +40,4 @@ done
interimap || error
check_mailbox_status "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/starttls-injection/imapd b/tests/starttls-injection/imapd
new file mode 100755
index 0000000..52cbe9a
--- /dev/null
+++ b/tests/starttls-injection/imapd
@@ -0,0 +1,77 @@
+#!/usr/bin/perl -T
+
+use warnings;
+use strict;
+
+use Errno qw/EINTR/;
+use Net::SSLeay qw/die_now/;
+use Socket qw/INADDR_LOOPBACK AF_INET SOCK_STREAM pack_sockaddr_in
+ SOL_SOCKET SO_REUSEADDR SHUT_RDWR/;
+
+BEGIN {
+ Net::SSLeay::load_error_strings();
+ Net::SSLeay::SSLeay_add_ssl_algorithms();
+ Net::SSLeay::randomize();
+}
+
+socket(my $S, AF_INET, SOCK_STREAM, 0) or die;
+setsockopt($S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
+bind($S, pack_sockaddr_in(10143, INADDR_LOOPBACK)) or die "bind: $!\n";
+listen($S, 1) or die "listen: $!";
+
+my $CONFDIR = $ENV{HOME} =~ /\A(\p{Print}+)\z/ ? "$1/.dovecot/conf.d" : die;
+my $CTX = Net::SSLeay::CTX_new() or die_now("SSL_CTX_new()");
+Net::SSLeay::CTX_set_mode($CTX,
+ Net::SSLeay::MODE_ENABLE_PARTIAL_WRITE() |
+ Net::SSLeay::MODE_ACCEPT_MOVING_WRITE_BUFFER() |
+ Net::SSLeay::MODE_AUTO_RETRY() | # don't fail SSL_read on renegotiation
+ Net::SSLeay::MODE_RELEASE_BUFFERS() );
+Net::SSLeay::CTX_use_PrivateKey_file($CTX, "$CONFDIR/dovecot.rsa.key", &Net::SSLeay::FILETYPE_PEM)
+ or die_now("Can't load private key: $!");
+Net::SSLeay::CTX_use_certificate_file($CTX, "$CONFDIR/dovecot.rsa.crt", &Net::SSLeay::FILETYPE_PEM)
+ or die_now("Can't load certificate: $!");
+
+while (1) {
+ my $sockaddr = accept(my $conn, $S) or do {
+ next if $! == EINTR;
+ die "accept: $!";
+ };
+
+ $conn->printflush("* OK IMAP4rev1 Server\r\n");
+
+ $conn->getline() =~ /\A(\S+) CAPABILITY\r\n\z/ or die;
+ $conn->printflush("* CAPABILITY IMAP4rev1 STARTTLS\r\n");
+ $conn->printflush("$1 OK CAPABILITY completed\r\n");
+
+ $conn->getline() =~ /\A(\S+) STARTTLS\r\n\z/ or die;
+
+ # These responses preceed the TLS handshake hence are not authenticated!
+ $conn->print("$1 OK Begin TLS\r\n");
+ $conn->print("* CAPABILITY IMAP4rev1 LOGINDISABLED X-injected\r\n");
+ # Note: tag format must match Net::IMAP::InterIMAP->_cmd_init()
+ $conn->printf("%06d OK CAPABILITY injected\r\n", $1+1);
+ $conn->flush();
+
+ my $ssl = Net::SSLeay::new($CTX) or die_now("SSL_new()");
+ die_now("SSL_set_fd()") unless Net::SSLeay::set_fd($ssl, $conn) == 1;
+ die_now("SSL_accept()") unless Net::SSLeay::accept($ssl);
+
+ Net::SSLeay::ssl_read_CRLF($ssl) =~ /\A(\S+) CAPABILITY\r\n\z/ or die_now("SSL_read()");
+ Net::SSLeay::ssl_write_CRLF($ssl, "* CAPABILITY IMAP4rev1 AUTH=LOGIN\r\n$1 OK CAPABILITY completed");
+
+ Net::SSLeay::ssl_read_CRLF($ssl) =~ /\A(\S+) LOGIN .*\r\n\z/ or die_now("SSL_read()");
+ Net::SSLeay::ssl_write_CRLF($ssl, "$1 OK [CAPABILITY IMAP4rev1] LOGIN completed");
+
+ Net::SSLeay::free($ssl);
+ close($conn);
+
+ last;
+}
+
+END {
+ Net::SSLeay::CTX_free($CTX) if defined $CTX;
+ if (defined $S) {
+ shutdown($S, SHUT_RDWR) or warn "shutdown: $!";
+ close($S) or print STDERR "close: $!\n";
+ }
+}
diff --git a/tests/starttls-injection/interimap.remote b/tests/starttls-injection/interimap.remote
new file mode 120000
index 0000000..ad49677
--- /dev/null
+++ b/tests/starttls-injection/interimap.remote
@@ -0,0 +1 @@
+../starttls/interimap.remote \ No newline at end of file
diff --git a/tests/starttls-injection/remote.conf b/tests/starttls-injection/remote.conf
new file mode 100644
index 0000000..340a484
--- /dev/null
+++ b/tests/starttls-injection/remote.conf
@@ -0,0 +1,8 @@
+protocols {
+ imap = yes
+}
+service imap-login {
+ inet_listener imap {
+ port = 0
+ }
+}
diff --git a/tests/starttls-injection/t b/tests/starttls-injection/t
new file mode 100644
index 0000000..023baff
--- /dev/null
+++ b/tests/starttls-injection/t
@@ -0,0 +1,16 @@
+# Test unauthenticated response injection after the STARTTLS response
+# For background see https://gitlab.com/muttmua/mutt/-/issues/248
+
+env -i USER="remote" HOME="$HOME_remote" "$TESTDIR/imapd" & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+
+! interimap --debug || error
+
+# Make sure we show a warning but ignore ignore (unauthenticated) injected responses
+! grep -E 'remote: S: .*[ -]injected$' <"$STDERR" || error "unauthenticated response injection"
+grep -Fx 'remote: WARNING: Truncating non-empty output buffer (unauthenticated response injection?)' <"$STDERR" || error
+
+! grep -Fx 'remote: ERROR: Logins are disabled.' <"$STDERR" || error "injected capability wasn't ignored"
+grep -Fx 'remote: ERROR: Server did not advertise ENABLE (RFC 5161) capability.' <"$STDERR" || error "injected capability wasn't ignored"
+
+# vim: set filetype=bash :
diff --git a/tests/starttls-logindisabled/remote.conf b/tests/starttls-logindisabled/remote.conf
index be2d51e..86e033a 100644
--- a/tests/starttls-logindisabled/remote.conf
+++ b/tests/starttls-logindisabled/remote.conf
@@ -2,4 +2,6 @@
!include conf.d/ssl.conf
# trick dovecot into treating local connections as insecure
-imap_capability = +LOGINDISABLED
+imap_capability {
+ LOGINDISABLED = yes
+}
diff --git a/tests/starttls-logindisabled/t b/tests/starttls-logindisabled/t
index 0ac7465..b2bf87b 100644
--- a/tests/starttls-logindisabled/t
+++ b/tests/starttls-logindisabled/t
@@ -16,4 +16,4 @@ grep -Fx "remote: C: 000001 CAPABILITY" <"$STDERR" || error
# can't go further as the capability string still has the manually
# enforced 'LOGINDISABLED'
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/starttls/t b/tests/starttls/t
index 99a39c2..633997c 100644
--- a/tests/starttls/t
+++ b/tests/starttls/t
@@ -1,3 +1,7 @@
+ssl_server_cert_file="$(doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_server/cert_file)"
+X509_SHA256="$(openssl x509 -in "$ssl_server_cert_file" -noout -fingerprint -sha256 \
+ | sed -rn "/^.*=\\s*/ {s///p;q}" | tr -d : | tr "[A-Z]" "[a-z]")"
+
for ((i = 0; i < 32; i++)); do
u="$(shuf -n1 -e "local" "remote")"
sample_message | deliver -u "$u"
@@ -17,11 +21,10 @@ grep -Fx "STARTTLS" <"$TMPDIR/capabilities" || error
grep -Fx "remote: C: 000000 STARTTLS" <"$STDERR" || error
grep -Fx "remote: C: 000001 CAPABILITY" <"$STDERR" || error
-grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1" <"$STDERR" || error
-grep -Fx "remote: Peer certificate fingerprint: sha256\$35944e3bd3300d3ac310bb497a32cc1eef6931482a587ddbc95343740cdf1323" <"$STDERR" || error
-grep "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
+grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error
+grep "^remote: SSL protocol: TLSv" <"$STDERR" || error
grep "^remote: SSL cipher: " <"$STDERR" || error
check_mailbox_status "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/sync-live-multi/local.conf b/tests/sync-live-multi/local.conf
index baae39d..6d64a84 100644
--- a/tests/sync-live-multi/local.conf
+++ b/tests/sync-live-multi/local.conf
@@ -1,30 +1,34 @@
namespace inbox {
- separator = /
- location = dbox:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
+ separator = /
+ mail_driver = sdbox
+ mail_path = ~/inbox
+ inbox = yes
+ list = yes
}
namespace foo {
- separator = /
- prefix = foo/
- location = dbox:~/foo:LAYOUT=index
- inbox = no
- list = yes
+ separator = /
+ prefix = foo/
+ mail_driver = sdbox
+ mail_path = ~/foo
+ inbox = no
+ list = yes
}
namespace bar {
- separator = /
- prefix = bar/
- location = dbox:~/bar:LAYOUT=index
- inbox = no
- list = yes
+ separator = /
+ prefix = bar/
+ mail_driver = sdbox
+ mail_path = ~/bar
+ inbox = no
+ list = yes
}
namespace baz {
separator = /
- prefix = baz/
- location = dbox:~/baz:LAYOUT=index
- inbox = no
- list = yes
+ prefix = baz/
+ mail_driver = sdbox
+ mail_path = ~/baz
+ inbox = no
+ list = yes
}
diff --git a/tests/sync-live-multi/t b/tests/sync-live-multi/t
index ba7f326..5a651f5 100644
--- a/tests/sync-live-multi/t
+++ b/tests/sync-live-multi/t
@@ -49,15 +49,15 @@ while [ $(date +%s) -le $timer ]; do
remote3) m="${m//\//\?}";;
*) error "Uh?";;
esac
- doveadm -u "$u" mailbox create -- "$m"
+ doveadm -u "$u" mailbox create --no-userdb-lookup -- "$m"
fi
# EXPUNGE some messages
u="$(shuf -n1 -e -- "${TARGETS[@]}")" # choose target at random
n="$(shuf -n1 -i0-3)"
while read guid uid; do
- doveadm -u "$u" expunge mailbox-guid "$guid" uid "$uid"
- done < <(doveadm -u "$u" search all | shuf -n "$n")
+ doveadm -u "$u" expunge --no-userdb-lookup mailbox-guid "$guid" uid "$uid"
+ done < <(doveadm -u "$u" search --no-userdb-lookup all | shuf -n "$n")
# mark some existing messages as read (toggle \Seen flag as unlike other
# flags it's easier to query and check_mailboxes_status checks it)
@@ -65,8 +65,8 @@ while [ $(date +%s) -le $timer ]; do
n="$(shuf -n1 -i0-9)"
while read guid uid; do
a="$(shuf -n1 -e add remove replace)"
- doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid"
- done < <(doveadm -u "$u" search all | shuf -n "$n")
+ doveadm -u "$u" flags "$a" --no-userdb-lookup "\\Seen" mailbox-guid "$guid" uid "$uid"
+ done < <(doveadm -u "$u" search --no-userdb-lookup all | shuf -n "$n")
# select at random a mailbox where to deliver some messages
u="$(shuf -n1 -e "local" "remote")" # choose target at random
@@ -103,14 +103,14 @@ trap - EXIT INT TERM
# check that the mailbox lists match
diff -u --label="local/mailboxes" --label="remote1/mailboxes" \
- <( doveadm -u "local" mailbox list | sed -n "s,^foo/,,p" | sort ) \
- <( doveadm -u "remote1" mailbox list | tr '^' '/' | sort )
+ <( doveadm -u "local" mailbox list --no-userdb-lookup | sed -n "s,^foo/,,p" | sort ) \
+ <( doveadm -u "remote1" mailbox list --no-userdb-lookup | tr '^' '/' | sort )
diff -u --label="local/mailboxes" --label="remote2/mailboxes" \
- <( doveadm -u "local" mailbox list | sed -n "s,^bar/,,p" | sort ) \
- <( doveadm -u "remote2" mailbox list | tr '\\' '/' | sort )
+ <( doveadm -u "local" mailbox list --no-userdb-lookup | sed -n "s,^bar/,,p" | sort ) \
+ <( doveadm -u "remote2" mailbox list --no-userdb-lookup | tr '\\' '/' | sort )
diff -u --label="local/mailboxes" --label="remote3/mailboxes" \
- <( doveadm -u "local" mailbox list | sed -n "s,^baz/,,p" | sort ) \
- <( doveadm -u "remote3" mailbox list | tr '?' '/' | sort )
+ <( doveadm -u "local" mailbox list --no-userdb-lookup | sed -n "s,^baz/,,p" | sort ) \
+ <( doveadm -u "remote3" mailbox list --no-userdb-lookup | tr '?' '/' | sort )
for m in "${MAILBOXES[@]}"; do
case "$m" in
@@ -124,4 +124,4 @@ for m in "${MAILBOXES[@]}"; do
check_mailbox_status2 "$blob" "$m" "$u" "$mr"
done
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/sync-live/t b/tests/sync-live/t
index 5f5b291..9b2074b 100644
--- a/tests/sync-live/t
+++ b/tests/sync-live/t
@@ -26,15 +26,15 @@ while [ $(date +%s) -le $timer ]; do
MAILBOXES+=( "$m" )
u="$(shuf -n1 -e "local" "remote")" # choose target at random
[ "$u" = "local" ] || m="${m//./^}"
- doveadm -u "$u" mailbox create -- "$m"
+ doveadm -u "$u" mailbox create --no-userdb-lookup -- "$m"
fi
# EXPUNGE some messages
u="$(shuf -n1 -e "local" "remote")" # choose target at random
n="$(shuf -n1 -i0-3)"
while read guid uid; do
- doveadm -u "$u" expunge mailbox-guid "$guid" uid "$uid"
- done < <(doveadm -u "$u" search all | shuf -n "$n")
+ doveadm -u "$u" expunge --no-userdb-lookup mailbox-guid "$guid" uid "$uid"
+ done < <(doveadm -u "$u" search --no-userdb-lookup all | shuf -n "$n")
# mark some existing messages as read (toggle \Seen flag as unlike other
# flags it's easier to query and check_mailboxes_status checks it)
@@ -42,8 +42,8 @@ while [ $(date +%s) -le $timer ]; do
n="$(shuf -n1 -i0-9)"
while read guid uid; do
a="$(shuf -n1 -e add remove replace)"
- doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid"
- done < <(doveadm -u "$u" search all | shuf -n "$n")
+ doveadm -u "$u" flags "$a" --no-userdb-lookup "\\Seen" mailbox-guid "$guid" uid "$uid"
+ done < <(doveadm -u "$u" search --no-userdb-lookup all | shuf -n "$n")
# select at random a mailbox where to deliver some messages
u="$(shuf -n1 -e "local" "remote")" # choose target at random
@@ -73,4 +73,4 @@ trap - EXIT INT TERM
check_mailbox_list
check_mailboxes_status "${MAILBOXES[@]}"
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/sync-mailbox-list/t b/tests/sync-mailbox-list/t
index ea80fbf..ba533f0 100644
--- a/tests/sync-mailbox-list/t
+++ b/tests/sync-mailbox-list/t
@@ -4,10 +4,10 @@
# baz: present on both, subscribed to remote only
# foo.bar: present on local only
# foo.baz: present on remote only
-doveadm -u "local" mailbox create "foo" "bar" "baz" "foo.bar" "fo!o [b*a%r]"
-doveadm -u "local" mailbox subscribe "foo" "bar"
-doveadm -u "remote" mailbox create "foo" "bar" "baz" "foo~baz" "foo]bar"
-doveadm -u "remote" mailbox subscribe "foo" "baz"
+doveadm -u "local" mailbox create --no-userdb-lookup "foo" "bar" "baz" "foo.bar" "fo!o [b*a%r]"
+doveadm -u "local" mailbox subscribe --no-userdb-lookup "foo" "bar"
+doveadm -u "remote" mailbox create --no-userdb-lookup "foo" "bar" "baz" "foo~baz" "foo]bar"
+doveadm -u "remote" mailbox subscribe --no-userdb-lookup "foo" "baz"
populate() {
local i
@@ -45,7 +45,7 @@ step_start "aborts if present in database"
for u in "local" "remote"; do
[ "$u" = "local" ] && { m="foo.bar"; m2="$m"; } || { m="foo.baz"; m2="foo~baz"; }
- doveadm -u "$u" mailbox delete "$m2"
+ doveadm -u "$u" mailbox delete --no-userdb-lookup "$m2"
! interimap || error
grep -Fx "database: ERROR: Mailbox $m exists. Run \`interimap --target=database --delete $m\` to delete." <"$STDERR"
@@ -64,10 +64,10 @@ step_done
# (un)subscribe from some mailboxes, including a non-existent one
step_start "new (un)subscribtions"
-doveadm -u "local" mailbox unsubscribe "foo"
-doveadm -u "remote" mailbox unsubscribe "bar"
-doveadm -u "local" mailbox subscribe "foo.bar" "foo.nonexistent" "foo.baz"
-doveadm -u "remote" mailbox subscribe "foo~bar" "bar~nonexistent"
+doveadm -u "local" mailbox unsubscribe --no-userdb-lookup "foo"
+doveadm -u "remote" mailbox unsubscribe --no-userdb-lookup "bar"
+doveadm -u "local" mailbox subscribe --no-userdb-lookup "foo.bar" "foo.nonexistent" "foo.baz"
+doveadm -u "remote" mailbox subscribe --no-userdb-lookup "foo~bar" "bar~nonexistent"
populate
interimap
@@ -75,7 +75,7 @@ grep -Fx "remote: Unsubscribe to foo" <"$STDERR"
grep -Fx "local: Unsubscribe to bar" <"$STDERR"
grep -Fx "remote: Subscribe to foo~baz" <"$STDERR"
verify
-check_mailbox_list -s $(doveadm -u "local" mailbox list) # exclude "foo.nonexistent" and "bar~nonexistent"
+check_mailbox_list -s $(doveadm -u "local" mailbox list --no-userdb-lookup) # exclude "foo.nonexistent" and "bar~nonexistent"
# check that "baz", "foo.bar" and "foo.baz" are the only subscribed mailboxes
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
@@ -90,4 +90,4 @@ EOF
[ $(< "$TMPDIR/count") -eq 0 ] || error
step_done
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/tls-ciphers/interimap.remote b/tests/tls-ciphers/interimap.remote
new file mode 120000
index 0000000..daf3741
--- /dev/null
+++ b/tests/tls-ciphers/interimap.remote
@@ -0,0 +1 @@
+../tls/interimap.remote \ No newline at end of file
diff --git a/tests/tls-ciphers/remote.conf b/tests/tls-ciphers/remote.conf
new file mode 120000
index 0000000..6029749
--- /dev/null
+++ b/tests/tls-ciphers/remote.conf
@@ -0,0 +1 @@
+../tls/remote.conf \ No newline at end of file
diff --git a/tests/tls-ciphers/t b/tests/tls-ciphers/t
new file mode 100644
index 0000000..ca0e610
--- /dev/null
+++ b/tests/tls-ciphers/t
@@ -0,0 +1,31 @@
+# backup config
+install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
+with_remote_config() {
+ install -m0600 "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config"
+ cat >>"$XDG_CONFIG_HOME/interimap/config"
+}
+
+with_remote_config <<-EOF
+ SSL_protocol_max = TLSv1.2
+ SSL_cipherlist = DHE-RSA-AES128-SHA256:ALL:!COMPLEMENTOFDEFAULT:!eNULL
+EOF
+interimap --debug || error
+grep -Fx "remote: SSL cipher: DHE-RSA-AES128-SHA256 (128 bits)" <"$STDERR" || error
+
+with_remote_config <<-EOF
+ SSL_protocol_max = TLSv1.2
+ SSL_cipherlist = NONEXISTENT:ECDHE-RSA-AES256-SHA384:ALL:!COMPLEMENTOFDEFAULT:!eNULL
+ SSL_ciphersuites = TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
+EOF
+interimap --debug || error
+grep -Fx "remote: SSL cipher: ECDHE-RSA-AES256-SHA384 (256 bits)" <"$STDERR" || error
+
+with_remote_config <<-EOF
+ SSL_protocol_min = TLSv1.3
+ SSL_cipherlist = DHE-RSA-AES128-SHA256
+ SSL_ciphersuites = TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
+EOF
+interimap --debug || error
+grep -Fx "remote: SSL cipher: TLS_CHACHA20_POLY1305_SHA256 (256 bits)" <"$STDERR" || error
+
+# vim: set filetype=bash :
diff --git a/tests/tls-pin-fingerprint/t b/tests/tls-pin-fingerprint/t
index 1b84390..679eaa4 100644
--- a/tests/tls-pin-fingerprint/t
+++ b/tests/tls-pin-fingerprint/t
@@ -1,3 +1,10 @@
+ssl_server_cert_file="$(doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_server/cert_file)"
+PKEY_SHA256="$(openssl x509 -in "$ssl_server_cert_file" -pubkey \
+ | openssl pkey -in /dev/stdin -pubin -outform DER \
+ | openssl dgst -sha256 | sed -rn "/^.*=\\s*/ {s///p;q}")"
+INVALID_FPR="sha256\$deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+INVALID_FPR2="sha256\$deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbee2"
+
# backup config
install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
with_remote_config() {
@@ -7,7 +14,7 @@ with_remote_config() {
# pinned valid fingerprint
with_remote_config <<-EOF
- SSL_fingerprint = sha256\$e8fc8d03ffe75e03897136a2f1c5647bf8c36be7136a6883a732a8c4961c1614
+ SSL_fingerprint = sha256\$$PKEY_SHA256
EOF
for ((i = 0; i < 32; i++)); do
@@ -18,16 +25,54 @@ interimap_init
check_mailbox_status "INBOX"
+# with default algorithm (SHA256)
+with_remote_config <<-EOF
+ SSL_fingerprint = $INVALID_FPR $PKEY_SHA256
+EOF
+interimap --debug || error
+grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error
+
+
# and now an invalid one
with_remote_config <<-EOF
- SSL_fingerprint = sha256\$deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+ SSL_fingerprint = $INVALID_FPR
+EOF
+! interimap --debug || error
+
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+grep -Fx "remote: WARNING: Fingerprint doesn't match! MiTM in action?" <"$STDERR" || error
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+# make sure we didn't send any credentials or started speaking IMAP
+! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+grep -Fx "remote: IMAP traffic (bytes): recv 0 sent 0" <"$STDERR" || error
+
+# two invalid ones
+with_remote_config <<-EOF
+ SSL_fingerprint = $INVALID_FPR $INVALID_FPR2
EOF
! interimap --debug || error
grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
grep -Fx "remote: WARNING: Fingerprint doesn't match! MiTM in action?" <"$STDERR" || error
grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
-# make sure we didn't send any credentials
+# make sure we didn't send any credentials or started speaking IMAP
! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+grep -Fx "remote: IMAP traffic (bytes): recv 0 sent 0" <"$STDERR" || error
+
+
+# valid + invalid
+with_remote_config <<-EOF
+ SSL_fingerprint = sha256\$$PKEY_SHA256 $INVALID_FPR
+EOF
+interimap --debug || error
+grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error
+
+
+# invalid + valid
+with_remote_config <<-EOF
+ SSL_fingerprint = $INVALID_FPR sha256\$$PKEY_SHA256
+EOF
+interimap --debug || error
+grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/tls-protocols/openssl.cnf b/tests/tls-protocols/openssl.cnf
new file mode 100644
index 0000000..3d9769d
--- /dev/null
+++ b/tests/tls-protocols/openssl.cnf
@@ -0,0 +1,14 @@
+# as we want to test TLSv1 we need to set MinProtocol=None, see
+# see /usr/share/doc/libssl1.1/NEWS.Debian.gz
+
+openssl_conf = default_conf
+
+[default_conf]
+ssl_conf = ssl_sect
+
+[ssl_sect]
+system_default = system_default_sect
+
+[system_default_sect]
+MinProtocol = None
+CipherString = DEFAULT@SECLEVEL=0
diff --git a/tests/tls-protocols/remote.conf b/tests/tls-protocols/remote.conf
index 6029749..96b3713 120000..100644
--- a/tests/tls-protocols/remote.conf
+++ b/tests/tls-protocols/remote.conf
@@ -1 +1,4 @@
-../tls/remote.conf \ No newline at end of file
+!include conf.d/imapd.conf
+!include conf.d/ssl.conf
+ssl_min_protocol = TLSv1
+ssl_cipher_list = DEFAULT@SECLEVEL=0
diff --git a/tests/tls-protocols/t b/tests/tls-protocols/t
index f34a95b..b78dd69 100644
--- a/tests/tls-protocols/t
+++ b/tests/tls-protocols/t
@@ -1,3 +1,13 @@
+# system default
+interimap --debug || error
+! grep -E "^remote: Disabling SSL protocols: " <"$STDERR" || error # TODO deprecated
+! grep -E "^remote: Minimum SSL/TLS protocol version: " <"$STDERR" || error
+! grep -E "^remote: Maximum SSL/TLS protocol version: " <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv" <"$STDERR" || error
+
+# load custom OpenSSL configuration to allow TLS protocol version <=1.1
+export OPENSSL_CONF="$TESTDIR/openssl.cnf"
+
# backup config
install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
with_remote_tls_protocols() {
@@ -5,17 +15,15 @@ with_remote_tls_protocols() {
printf "SSL_protocols = %s\\n" "$*" >>"$XDG_CONFIG_HOME/interimap/config"
}
-# default
-interimap --debug || error
-grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1" <"$STDERR" || error
-grep -E "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
-
-# also disable TLSv1.2
+# disable TLSv1.2 and earlier
with_remote_tls_protocols "!SSLv2" "!SSLv3" "!TLSv1" "!TLSv1.1" "!TLSv1.2"
interimap --debug || error
grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1, TLSv1.2" <"$STDERR" || error
grep -E "^remote: SSL protocol: TLSv1\.3 " <"$STDERR" || error
+interimap || error
+grep -E "^remote: WARNING: SSL_protocols is deprecated " <"$STDERR" || error "no deprecation warning"
+
# force TLSv1.2
with_remote_tls_protocols "TLSv1.2"
interimap --debug || error
@@ -28,12 +36,64 @@ interimap --debug || error
grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1.3" <"$STDERR" || error
grep -E "^remote: SSL protocol: TLSv(1\.[12])? " <"$STDERR" || error
-# force SSLv2 and SSLv3, fails as it's disabled server side
+# force SSLv2 and SSLv3; this fails due to dovecot's ssl_min_protocol=TLSv1
with_remote_tls_protocols "SSLv2" "SSLv3"
! interimap --debug || error
grep -Fx "remote: Disabling SSL protocols: TLSv1, TLSv1.1, TLSv1.2, TLSv1.3" <"$STDERR" || error
grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
-# make sure we didn't send any credentials
+# make sure we didn't send any credentials or started speaking IMAP
+! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+grep -Fx "remote: IMAP traffic (bytes): recv 0 sent 0" <"$STDERR" || error
+
+
+# new interface: SSL_protocol_{min,max}
+with_remote_tls_protocol_min_max() {
+ install -m0600 "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config"
+ if [ -n "${1-}" ]; then
+ printf "SSL_protocol_min = %s\\n" "$1" >>"$XDG_CONFIG_HOME/interimap/config"
+ fi
+ if [ -n "${2-}" ]; then
+ printf "SSL_protocol_max = %s\\n" "$2" >>"$XDG_CONFIG_HOME/interimap/config"
+ fi
+}
+
+# disable TLSv1.2 and earlier
+# XXX this test assumes that TLSv1.3 is the highest version supported
+with_remote_tls_protocol_min_max "TLSv1.3"
+interimap --debug || error
+grep -Fx "remote: Minimum SSL/TLS protocol version: TLSv1.3" <"$STDERR" || error
+! grep -E "^remote: Maximum SSL/TLS protocol version: " <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv1\.3 " <"$STDERR" || error
+
+# force TLSv1.2
+with_remote_tls_protocol_min_max "TLSv1.2" "TLSv1.2"
+interimap --debug || error
+grep -Fx "remote: Minimum SSL/TLS protocol version: TLSv1.2" <"$STDERR" || error
+grep -Fx "remote: Maximum SSL/TLS protocol version: TLSv1.2" <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv1\.2 " <"$STDERR" || error
+
+# disable TLSv1.2 and later
+with_remote_tls_protocol_min_max "" "TLSv1.1"
+interimap --debug || error
+! grep -E "^remote: Minimum SSL/TLS protocol version: " <"$STDERR" || error
+grep -Fx "remote: Maximum SSL/TLS protocol version: TLSv1.1" <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv1\.1 " <"$STDERR" || error
+
+# force SSLv3 to to TLSv1.1
+with_remote_tls_protocol_min_max "SSLv3" "TLSv1.1"
+interimap --debug || error
+grep -Fx "remote: Minimum SSL/TLS protocol version: SSLv3" <"$STDERR" || error
+grep -Fx "remote: Maximum SSL/TLS protocol version: TLSv1.1" <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv1(\.1)? " <"$STDERR" || error
+
+# force SSLv3; this fails due to dovecot's ssl_min_protocol=TLSv1
+with_remote_tls_protocol_min_max "SSLv3" "SSLv3"
+! interimap --debug || error
+grep -Fx "remote: Minimum SSL/TLS protocol version: SSLv3" <"$STDERR" || error
+grep -Fx "remote: Maximum SSL/TLS protocol version: SSLv3" <"$STDERR" || error
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+# make sure we didn't send any credentials or started speaking IMAP
! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+grep -Fx "remote: IMAP traffic (bytes): recv 0 sent 0" <"$STDERR" || error
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/tls-rsa+ecdsa/interimap.remote b/tests/tls-rsa+ecdsa/interimap.remote
new file mode 120000
index 0000000..daf3741
--- /dev/null
+++ b/tests/tls-rsa+ecdsa/interimap.remote
@@ -0,0 +1 @@
+../tls/interimap.remote \ No newline at end of file
diff --git a/tests/tls-rsa+ecdsa/remote.conf b/tests/tls-rsa+ecdsa/remote.conf
new file mode 100644
index 0000000..c0f2ff3
--- /dev/null
+++ b/tests/tls-rsa+ecdsa/remote.conf
@@ -0,0 +1,5 @@
+!include conf.d/imapd.conf
+!include conf.d/ssl.conf
+
+ssl_server_alt_cert_file = conf.d/dovecot.ecdsa.crt
+ssl_server_alt_key_file = conf.d/dovecot.ecdsa.key
diff --git a/tests/tls-rsa+ecdsa/t b/tests/tls-rsa+ecdsa/t
new file mode 100644
index 0000000..789d9e6
--- /dev/null
+++ b/tests/tls-rsa+ecdsa/t
@@ -0,0 +1,59 @@
+doveconf_remote() {
+ local p k="$1"
+ p="$(doveconf -c "$HOME_remote/.dovecot/config" -hx "$1")"
+ cat <"$p"
+}
+pkey_sha256() {
+ openssl x509 -in /dev/stdin -pubkey \
+ | openssl pkey -in /dev/stdin -pubin -outform DER \
+ | openssl dgst -sha256 | sed -rn "/^.*=\\s*/ {s///p;q}"
+}
+x509_sha256() {
+ openssl x509 -in /dev/stdin -noout -fingerprint -sha256 \
+ | sed -rn "/^.*=\\s*/ {s///p;q}" | tr -d : | tr "[A-Z]" "[a-z]"
+}
+
+PKEY_SHA256="$(doveconf_remote ssl_server/cert_file | pkey_sha256)"
+X509_SHA256="$(doveconf_remote ssl_server/cert_file | x509_sha256)"
+PKEY_ALT_SHA256="$(doveconf_remote ssl_server/alt_cert_file | pkey_sha256)"
+X509_ALT_SHA256="$(doveconf_remote ssl_server/alt_cert_file | x509_sha256)"
+
+# pinned valid fingerprints
+cat >>"$XDG_CONFIG_HOME/interimap/config" <<-EOF
+ SSL_fingerprint = sha256\$$PKEY_SHA256 sha256\$$PKEY_ALT_SHA256
+EOF
+
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+interimap_init
+check_mailbox_status "INBOX"
+
+interimap --debug || error
+# which peer certificate is used is up to libssl
+grep -Fx -e "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" \
+ -e "remote: Peer certificate fingerprint: sha256\$$X509_ALT_SHA256" \
+ <"$STDERR" || error
+grep -Fx -e "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" \
+ -e "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_ALT_SHA256" \
+ <"$STDERR" || error
+
+# force RSA
+# XXX we also have to force TLS <=1.2 here as the TLSv1.3 ciphersuites
+# don't specify the certificate type (nor key exchange)
+cat >>"$XDG_CONFIG_HOME/interimap/config" <<-EOF
+ SSL_protocol_max = TLSv1.2
+ SSL_cipherlist = EECDH+AESGCM+aRSA
+EOF
+interimap --debug || error
+grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error
+grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error
+
+# force ECDSA
+sed -i "s/^SSL_cipherlist\\s*=.*/SSL_cipherlist = EECDH+AESGCM+aECDSA/" -- "$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || error
+grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_ALT_SHA256" <"$STDERR" || error
+grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_ALT_SHA256" <"$STDERR" || error
+
+# vim: set filetype=bash :
diff --git a/tests/tls-sni/interimap.remote b/tests/tls-sni/interimap.remote
new file mode 100644
index 0000000..9f0d521
--- /dev/null
+++ b/tests/tls-sni/interimap.remote
@@ -0,0 +1,3 @@
+type = imaps
+port = 10993
+SSL_verify = no
diff --git a/tests/tls-sni/remote.conf b/tests/tls-sni/remote.conf
new file mode 100644
index 0000000..ef76cf9
--- /dev/null
+++ b/tests/tls-sni/remote.conf
@@ -0,0 +1,7 @@
+!include conf.d/imapd.conf
+!include conf.d/ssl.conf
+
+local_name imap.example.net {
+ ssl_server_cert_file = conf.d/dovecot.rsa2.crt
+ ssl_server_key_file = conf.d/dovecot.rsa2.key
+}
diff --git a/tests/tls-sni/t b/tests/tls-sni/t
new file mode 100644
index 0000000..7692f74
--- /dev/null
+++ b/tests/tls-sni/t
@@ -0,0 +1,66 @@
+SERVERNAME="imap.example.net" # cf local_name{} section in the dovecot config
+ssl_server_cert_file="$(doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_server/cert_file)"
+X509_SHA256="$(openssl x509 -in "$ssl_server_cert_file" -noout -fingerprint -sha256 \
+ | sed -rn "/^.*=\\s*/ {s///p;q}" | tr -d : | tr "[A-Z]" "[a-z]")"
+ssl_server_cert_file2="$(doveconf -c "$HOME_remote/.dovecot/config" -f local_name="$SERVERNAME" -hx ssl_server/cert_file)"
+X509_2_SHA256="$(openssl x509 -in "$ssl_server_cert_file2" -noout -fingerprint -sha256 \
+ | sed -rn "/^.*=\\s*/ {s///p;q}" | tr -d : | tr "[A-Z]" "[a-z]")"
+
+# check that empty SSL_hostname disables SNI
+echo "SSL_hostname =" >>"$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || error
+! grep "^remote: Using SNI with name " <"$STDERR" || error "Empty SSL_hostname didn't disable SNI"
+
+# default servername is the host value
+sed -i "/^SSL_hostname\\s*=/d" -- "$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || error
+grep -Fx "remote: Using SNI with name localhost" <"$STDERR" || error "No default SNI"
+grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error
+
+# verify that SNI is not used when host is an IP
+echo "host = __INVALID__" >>"$XDG_CONFIG_HOME/interimap/config"
+for ip in "127.0.0.1" "[::1]"; do
+ sed -i "s/^host\\s*=.*/host = $ip/" -- "$XDG_CONFIG_HOME/interimap/config"
+ interimap --debug || error
+ ! grep "^remote: Using SNI with name " <"$STDERR" || error "Using SNI with IP $ip"
+ grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error
+done
+
+# verify that SNI actually works (ie we're served the right cert)
+sni_ok() {
+ grep -Fx "remote: Using SNI with name $SERVERNAME" <"$STDERR" || error
+ grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_2_SHA256" <"$STDERR" || error
+}
+echo "SSL_hostname = $SERVERNAME" >>"$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || error
+sni_ok
+
+
+## make sure SSL_hostname doesn't affect certificate verification ##
+
+# bad CA, bad host
+sed -i "s/^host\\s*=.*/host = 127.0.0.1/" -- "$XDG_CONFIG_HOME/interimap/config"
+sed -i "s/^SSL_verify\\s*=.*/SSL_verify = YES/" -- "$XDG_CONFIG_HOME/interimap/config"
+! interimap --debug || error
+sni_ok
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+
+# good CA, bad host
+echo "SSL_CAfile = $HOME/.dovecot/conf.d/ca.crt" >>"$XDG_CONFIG_HOME/interimap/config"
+! interimap --debug || error
+sni_ok
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+
+# bad CA, good host
+sed -i "/^SSL_CAfile\\s*=/d" -- "$XDG_CONFIG_HOME/interimap/config"
+sed -i "s/^host\\s*=.*/host = localhost/" -- "$XDG_CONFIG_HOME/interimap/config"
+! interimap --debug || error
+sni_ok
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+
+# good CA, good host
+echo "SSL_CAfile = $HOME/.dovecot/conf.d/ca.crt" >>"$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || error
+sni_ok
+
+# vim: set filetype=bash :
diff --git a/tests/tls-verify-peer/interimap.remote b/tests/tls-verify-peer/interimap.remote
index b02fcd0..263655f 100644
--- a/tests/tls-verify-peer/interimap.remote
+++ b/tests/tls-verify-peer/interimap.remote
@@ -1,2 +1 @@
-host = ::1
port = 10993
diff --git a/tests/tls-verify-peer/t b/tests/tls-verify-peer/t
index d84328a..50c7445 100644
--- a/tests/tls-verify-peer/t
+++ b/tests/tls-verify-peer/t
@@ -1,80 +1,149 @@
-CERT=~/.dovecot/conf.d/dovecot.pem
+ssl_server_cert_file="$(doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_server/cert_file)"
+X509_SHA256="$(openssl x509 -in "$ssl_server_cert_file" -noout -fingerprint -sha256 \
+ | sed -rn "/^.*=\\s*/ {s///p;q}" | tr -d : | tr "[A-Z]" "[a-z]")"
+PKEY_SHA256="$(openssl x509 -in "$ssl_server_cert_file" -pubkey \
+ | openssl pkey -in /dev/stdin -pubin -outform DER \
+ | openssl dgst -sha256 | sed -rn "/^.*=\\s*/ {s///p;q}")"
unverified_peer() {
! interimap --debug || error
+ # make sure we aborted the handshake immediately after connecting
+ grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error
grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
sed -nr "s/remote: \[[0-9]+\] (preverify=[0-9]+)$/\1/p" <"$STDERR" >"$TMPDIR/preverify"
[ -s "$TMPDIR/preverify" ] || error
! grep -Fvx "preverify=0" <"$TMPDIR/preverify" || error
- # make sure we didn't send any credentials
+ # make sure we didn't send any credentials or started speaking IMAP
! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+ grep -Fx "remote: IMAP traffic (bytes): recv 0 sent 0" <"$STDERR" || error
}
verified_peer() {
local i u
- for ((i = 0; i < 32; i++)); do
+ for ((i = 0; i < 4; i++)); do
u="$(shuf -n1 -e "local" "remote")"
sample_message | deliver -u "$u"
done
interimap --debug || error
+ grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error
sed -nr "s/remote: \[[0-9]+\] (preverify=[0-9]+)$/\1/p" <"$STDERR" >"$TMPDIR/preverify"
[ -s "$TMPDIR/preverify" ] || error
! grep -Fvx "preverify=1" <"$TMPDIR/preverify" || error
- grep "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
+ grep "^remote: SSL protocol: TLSv" <"$STDERR" || error
grep "^remote: SSL cipher: " <"$STDERR" || error
check_mailbox_status "INBOX"
}
# backup config
-install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
+install -m0600 -- "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
with_remote_config() {
- install -m0600 "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config"
+ install -m0600 -- "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config"
cat >>"$XDG_CONFIG_HOME/interimap/config"
}
step_start "peer verification enabled by default"
+# assume our fake root CA is not among OpenSSL's default trusted CAs
unverified_peer
+grep -Fx "remote: Using default locations for trusted CA certificates" <"$STDERR" || error
step_done
step_start "peer verification result honored when pinned pubkey matches"
-pkey_sha256="$(openssl x509 -pubkey <"$CERT" | openssl pkey -pubin -outform DER \
- | openssl dgst -sha256 | sed -rn "/^.*=\\s*/ {s///p;q}")"
with_remote_config <<-EOF
- SSL_fingerprint = sha256\$$pkey_sha256
+ SSL_fingerprint = sha256\$$PKEY_SHA256
EOF
unverified_peer
-! grep -Fx "remote: WARNING: Fingerprint doesn't match! MiTM in action?" <"$STDERR" || error
+grep -Fx "remote: Using default locations for trusted CA certificates" <"$STDERR" || error
+grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error
step_done
-step_start "SSL_CAfile"
+capath=$(mktemp --tmpdir="$TMPDIR" --directory capath.XXXXXX)
+cp -T -- ~/.dovecot/conf.d/ca.crt "$capath/ca-certificates.crt"
+
+step_start "SSL_CAfile/\$SSL_CERT_FILE"
+
+# verify that an error is raised when CAfile can't be loaded
+# (it's not the case for $SSL_CERT_FILE, cf. SSL_CTX_load_verify_locations(3ssl))
+with_remote_config <<<"SSL_CAfile = /nonexistent"
+! interimap --debug || error
+grep -Fx "remote: ERROR: SSL_CTX_load_verify_locations()" <"$STDERR" || error
+grep -Fx "remote: IMAP traffic (bytes): recv 0 sent 0" <"$STDERR" || error
+
if [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
- # the self-signed cert should not be in there
+ # assume our fake root CA is not there
with_remote_config <<<"SSL_CAfile = /etc/ssl/certs/ca-certificates.crt"
unverified_peer
fi
-with_remote_config <<<"SSL_CAfile = $CERT"
+
+# default host (localhost) is the CN (and also subjectAltName)
+with_remote_config <<<"SSL_CAfile = $capath/ca-certificates.crt"
verified_peer
+
+with_remote_config </dev/null
+SSL_CERT_FILE=~/.dovecot/conf.d/ca.crt verified_peer
+grep -Fx "remote: Using default locations for trusted CA certificates" <"$STDERR" || error
+
+# hostnames and IPs included in the subjectAltName should work as well
+for host in "ip6-localhost" "127.0.0.1" "::1"; do
+ with_remote_config <<-EOF
+ host = $host
+ SSL_CAfile = $capath/ca-certificates.crt
+ EOF
+ verified_peer
+done
+
+# but not for other hostnames or IPs
+for host in "ip6-loopback" "127.0.1.1"; do
+ with_remote_config <<-EOF
+ host = $host
+ SSL_CAfile = $capath/ca-certificates.crt
+ EOF
+ unverified_peer
+done
+
step_done
-step_start "SSL_CApath"
+step_start "SSL_CApath/\$SSL_CERT_DIR"
+
if [ -d "/etc/ssl/certs" ]; then
- # the self-signed cert should not be in there
+ # assume our fake root CA is not there
with_remote_config <<<"SSL_CApath = /etc/ssl/certs"
unverified_peer
fi
-capath=$(mktemp --tmpdir="$TMPDIR" --directory capath.XXXXXX)
-cp -t"$capath" "$CERT"
c_rehash "$capath"
+# default host (localhost) is the CN (and also subjectAltName)
with_remote_config <<<"SSL_CApath = $capath"
verified_peer
+
+with_remote_config </dev/null
+SSL_CERT_DIR="$capath" verified_peer
+grep -Fx "remote: Using default locations for trusted CA certificates" <"$STDERR" || error
+
+# hostnames and IPs included in the subjectAltName should work as well
+for host in "ip6-localhost" "127.0.0.1" "::1"; do
+ with_remote_config <<-EOF
+ host = $host
+ SSL_CApath = $capath
+ EOF
+ verified_peer
+done
+
+# but not for other IPs or hostnames
+for host in "ip6-loopback" "127.0.1.1"; do
+ with_remote_config <<-EOF
+ host = $host
+ SSL_CApath = $capath
+ EOF
+ unverified_peer
+done
+
step_done
-# vim: set filetype=sh :
+# vim: set filetype=bash :
diff --git a/tests/tls/t b/tests/tls/t
index dd6d955..fa59ae5 100644
--- a/tests/tls/t
+++ b/tests/tls/t
@@ -1,14 +1,17 @@
+ssl_server_cert_file="$(doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_server/cert_file)"
+X509_SHA256="$(openssl x509 -in "$ssl_server_cert_file" -noout -fingerprint -sha256 \
+ | sed -rn "/^.*=\\s*/ {s///p;q}" | tr -d : | tr "[A-Z]" "[a-z]")"
+
for ((i = 0; i < 32; i++)); do
u="$(shuf -n1 -e "local" "remote")"
sample_message | deliver -u "$u"
done
interimap --debug || error
-grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1" <"$STDERR" || error
-grep -Fx "remote: Peer certificate fingerprint: sha256\$35944e3bd3300d3ac310bb497a32cc1eef6931482a587ddbc95343740cdf1323" <"$STDERR" || error
-grep "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
+grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error
+grep "^remote: SSL protocol: TLSv" <"$STDERR" || error
grep "^remote: SSL cipher: " <"$STDERR" || error
check_mailbox_status "INBOX"
-# vim: set filetype=sh :
+# vim: set filetype=bash :