diff options
94 files changed, 1608 insertions, 695 deletions
@@ -1,5 +1,4 @@ *~ -/doc/*.1 -/doc/*.html -!/doc/template.html +*.bak +/build/ /.pc/ @@ -1,3 +1,163 @@ +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 @@ -33,7 +193,7 @@ interimap (0.5) 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 @@ -41,7 +201,7 @@ interimap (0.5) 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. @@ -104,7 +264,7 @@ interimap (0.5) 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.) @@ -118,7 +278,7 @@ interimap (0.5) 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 @@ -128,7 +288,7 @@ interimap (0.5) 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. @@ -1,61 +1,117 @@ -DESTDIR ?= /usr/local -BUILD_DOCDIR ?= ./doc +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 + CSS ?= /usr/share/javascript/bootstrap4/css/bootstrap.css -HTML_TEMPLATE ?= ./doc/template.html +HTML_TEMPLATE ?= $(srcdir)/doc/template.html -HTML_FILES = $(addprefix $(BUILD_DOCDIR)/,$(patsubst ./doc/%.md,%.html,$(wildcard ./doc/*.md))) -MANUAL_FILES = $(addprefix $(BUILD_DOCDIR)/,$(patsubst ./doc/%.md,%,$(wildcard ./doc/*.[1-9].md))) +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: manual +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 -$(MANUAL_FILES): $(BUILD_DOCDIR)/%: ./doc/%.md - pandoc -f markdown -t json -- "$<" | ./pandoc2man.jq | pandoc -s -f json -t man -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 "$@" + +$(SERVICE_FILES): $(builddir)/%.service: $(srcdir)/%.service + @mkdir -vp -- $(@D) + sed "s|@bindir@|$(bindir)|" <"$<" >"$@" -test: - @./tests/run-all +testcerts: + $(srcdir)/tests/certs/generate -## make html CSS="https://guilhem.org/static/css/bootstrap.min.css" BUILD_DOCDIR="$XDG_RUNTIME_DIR/Downloads" -$(HTML_FILES): $(BUILD_DOCDIR)/%.html: ./doc/%.md $(HTML_TEMPLATE) - mtime="$$(git --no-pager log -1 --pretty="format:%ct" -- "$<" 2>/dev/null)"; \ +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" + +$(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 -prefix ?= $(DESTDIR) -exec_prefix ?= $(prefix) -bindir ?= $(exec_prefix)/bin -libdir ?= $(exec_prefix)/lib -datarootdir ?= $(prefix)/share -mandir ?= $(datarootdir)/man -man1dir ?= $(mandir)/man1 +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: all - install -m0755 -vDt $(bindir) ./interimap ./pullimap - install -m0644 -vDT ./lib/Net/IMAP/InterIMAP.pm $(datarootdir)/perl5/Net/IMAP/InterIMAP.pm - install -m0644 -vDt $(man1dir) $(BUILD_DOCDIR)/interimap.1 $(BUILD_DOCDIR)/pullimap.1 - install -m0644 -vDt $(datarootdir)/doc/pullimap ./pullimap.sample - install -m0644 -vDt $(datarootdir)/doc/interimap ./interimap.sample ./doc/getting-started.md ./doc/multi-account.md README - install -m0644 -vDt $(libdir)/systemd/user ./*.service +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) uninstall: - rm -vf -- $(bindir)/interimap $(man1dir)/interimap.1 $(libdir)/systemd/user/interimap*.service - rm -vf -- $(bindir)/pullimap $(man1dir)/pullimap.1 $(libdir)/systemd/user/pullimap*.service - rm -vf -- $(datarootdir)/perl5/Net/IMAP/InterIMAP.pm - rm -rvf -- $(datarootdir)/doc/interimap $(datarootdir)/doc/pullimap - rm -vf -- $(BUILD_DOCDIR)/interimap.1 $(BUILD_DOCDIR)/pullimap.1 + 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 -vf -- $(MANUAL_FILES) $(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 uninstall 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 @@ -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/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 4a4f80d..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" - $ env 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 f4578b9..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 =================== @@ -88,7 +94,7 @@ 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 @@ -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,7 +150,7 @@ 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" \ + $ env -i PATH="$PATH" perl -T ./interimap --config="$BASEDIR/interimap.conf" \ --watch=1 --debug Use instructions from the [previous section][Mail storage access] @@ -168,12 +174,12 @@ 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" \ + $ 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" \ + $ 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] @@ -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/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 1d059b4..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 @@ -84,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 @@ -94,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 = / } @@ -113,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 @@ -198,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 @@ -281,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/interimap.1.md b/doc/interimap.1.md index 5370d79..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,29 +378,44 @@ 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. @@ -409,25 +426,47 @@ Valid options are: | 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} ==================== @@ -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 0055675..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,25 +202,39 @@ 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. @@ -229,25 +245,47 @@ Valid options are: | 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). @@ -372,5 +410,5 @@ Standards [`fetchmail`(1)]: https://www.fetchmail.info/ [`getmail`(1)]: http://pyropus.ca/software/getmail/ [`write`(2)]: https://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 +[`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 d825cde..c3809ad 100644 --- a/doc/template.html +++ b/doc/template.html @@ -14,7 +14,7 @@ $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;} @@ -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 @@ -22,17 +22,18 @@ use v5.14.2; use strict; use warnings; -our $VERSION = '0.5.2'; +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 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}; @@ -159,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, @@ -166,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); @@ -286,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 8b69e12..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 @@ -16,7 +16,7 @@ # 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: $!"); } } @@ -1308,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}; @@ -1367,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; @@ -1446,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) { @@ -1473,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 @@ -1492,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; } @@ -1504,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; @@ -1520,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; @@ -1553,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; @@ -1593,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 @@ -1614,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'; @@ -1624,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; } @@ -1643,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; @@ -1656,82 +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 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))); @@ -1766,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; @@ -1979,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; @@ -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 @@ -22,7 +22,7 @@ use v5.20.2; use strict; use warnings; -our $VERSION = '0.5.2'; +our $VERSION = '0.5.8'; my $NAME = 'pullimap'; use Errno 'EINTR'; @@ -31,7 +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 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}; @@ -104,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; @@ -117,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 } @@ -333,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 c2df4b5..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 176." <"$STDERR" \ - || error "Is \$DBH->do(\"PRAGMA locking_mode = EXCLUSIVE\"); at line 176?" +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 a8ea07e..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 172\." <"$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 db77f50..559daed 100644 --- a/tests/list +++ b/tests/interimap.list @@ -51,13 +51,13 @@ split-set Split large sets to avoid extra-long command lines 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 index 8f3ac30..bf2ed72 100755 --- a/tests/preauth-plaintext/imapd +++ b/tests/preauth-plaintext/imapd @@ -18,7 +18,7 @@ while (1) { die "accept: $!"; }; - # minimum CAPABILITY list, see tests/snippets/dovecot/interimap-required-capabilities.conf + # 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; @@ -39,6 +39,6 @@ while (1) { END { if (defined $S) { shutdown($S, SHUT_RDWR) or warn "shutdown: $!"; - close($S) or print STDERR "Can't close: $!\n"; + close($S) or print STDERR "close: $!\n"; } } diff --git a/tests/preauth-plaintext/t b/tests/preauth-plaintext/t index 427d57b..2f3071f 100644 --- a/tests/preauth-plaintext/t +++ b/tests/preauth-plaintext/t @@ -10,10 +10,10 @@ grep -Fx 'remote: ERROR: PREAUTH greeting on plaintext connection? MiTM in actio ! grep '^remote: C: ' <"$STDERR" || error "wrote command in MiTM'ed PREAUTH connection!" -# Ignore the warning when STARTTLS is explicitely disabled +# 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=sh : +# 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 0dfe634..1c57f8d 100644 --- a/tests/pullimap/t +++ b/tests/pullimap/t @@ -6,14 +6,32 @@ step_start "\`pullimap --idle\` refuses to create the state file" ! pullimap --idle "remote" || error step_done +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 "$fields" mailbox-guid "$guid" uid "$uid" | 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" \ @@ -73,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" @@ -91,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 @@ -99,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" @@ -136,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 : @@ -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,23 +218,32 @@ prepare interimap() { _interimap_cmd "interimap" "$@"; } pullimap() { _interimap_cmd "pullimap" "$@"; } _interimap_cmd() { - declare -a ENVIRON=() + declare -a ENVIRON=() args=() local script="$1" rv=0 shift environ_set "local" - env -i "${ENVIRON[@]}" perl -I./lib -T "./$script" "$@" 2>"$STDERR" || rv=$? + [ -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 @@ -333,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 @@ -344,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 @@ -392,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"}" @@ -442,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 d13f689..79e62d1 100755 --- a/tests/run-all +++ b/tests/run-all @@ -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 index 9000c8d..52cbe9a 100755 --- a/tests/starttls-injection/imapd +++ b/tests/starttls-injection/imapd @@ -4,7 +4,7 @@ use warnings; use strict; use Errno qw/EINTR/; -use Net::SSLeay qw/die_now die_if_ssl_error/; +use Net::SSLeay qw/die_now/; use Socket qw/INADDR_LOOPBACK AF_INET SOCK_STREAM pack_sockaddr_in SOL_SOCKET SO_REUSEADDR SHUT_RDWR/; @@ -20,16 +20,16 @@ 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"); +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.key", &Net::SSLeay::FILETYPE_PEM) - or die_if_ssl_error("Can't load private key: $!"); -Net::SSLeay::CTX_use_certificate_file($CTX, "$CONFDIR/dovecot.pem", &Net::SSLeay::FILETYPE_PEM) - or die_if_ssl_error("Can't load certificate: $!"); +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 { @@ -52,14 +52,14 @@ while (1) { $conn->printf("%06d OK CAPABILITY injected\r\n", $1+1); $conn->flush(); - my $ssl = Net::SSLeay::new($CTX) or die_if_ssl_error("SSL_new"); - Net::SSLeay::set_fd($ssl, $conn) or die_if_ssl_error("SSL_set_fd"); - Net::SSLeay::accept($ssl) and die_if_ssl_error("SSL_accept"); + 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_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_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); @@ -72,6 +72,6 @@ END { Net::SSLeay::CTX_free($CTX) if defined $CTX; if (defined $S) { shutdown($S, SHUT_RDWR) or warn "shutdown: $!"; - close($S) or print STDERR "Can't close: $!\n"; + close($S) or print STDERR "close: $!\n"; } } diff --git a/tests/starttls-injection/remote.conf b/tests/starttls-injection/remote.conf index f23f3de..340a484 100644 --- a/tests/starttls-injection/remote.conf +++ b/tests/starttls-injection/remote.conf @@ -1,4 +1,6 @@ -protocols = $protocols imap +protocols { + imap = yes +} service imap-login { inet_listener imap { port = 0 diff --git a/tests/starttls-injection/t b/tests/starttls-injection/t index d57aa7a..023baff 100644 --- a/tests/starttls-injection/t +++ b/tests/starttls-injection/t @@ -13,4 +13,4 @@ grep -Fx 'remote: WARNING: Truncating non-empty output buffer (unauthenticated r ! 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=sh : +# 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 : |