diff options
39 files changed, 530 insertions, 249 deletions
@@ -1,4 +1,5 @@ *~ +*.bak /doc/*.1 /doc/*.html !/doc/template.html @@ -1,18 +1,67 @@ +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 1.0.2. + 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`. Hence - running `make test` now requires OpenSSL 1.1.1 or later. + + 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 @@ -26,7 +75,7 @@ interimap (0.5.4) upstream; interimap (0.5.3) upstream; - * libinterimap: SSL_fingerprint now supports a space-separate list of + * 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 @@ -75,7 +124,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 @@ -83,7 +132,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. @@ -146,7 +195,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.) @@ -160,7 +209,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 @@ -170,7 +219,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. @@ -20,16 +20,21 @@ test: ./tests/run-all release: - @if ! git diff HEAD --quiet -- ./interimap ./pullimap ./Changelog; then \ + @if ! git diff HEAD --quiet -- ./Changelog ./interimap ./pullimap ./lib/Net/IMAP/InterIMAP.pm; then \ echo "Dirty state, refusing to release!" >&2; \ exit 1; \ fi - sed -ri "0,/^( -- .*) .*/ s//\1 $(shell date -R)/" ./Changelog VERS=$$(dpkg-parsechangelog -l Changelog -SVersion 2>/dev/null) && \ - sed -ri "0,/^(our \\\$$VERSION\\s*=\s*)'[0-9.]+'\\s*;/ s//\1'$$VERS';/" \ + if git 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)/" ./Changelog && \ + sed -ri "0,/^(our\\s+\\\$$VERSION\\s*=\\s*)'[0-9.]+'\\s*;/ s//\\1'$$VERS';/" \ + -- ./interimap ./pullimap && \ + sed -ri "0,/^(package\\s+Net::IMAP::InterIMAP\\s+)v[0-9.]+\\s*;/ s//\\1v$$VERS;/" \ + -- ./lib/Net/IMAP/InterIMAP.pm && \ + sed -ri "0,/^(use\\s+Net::IMAP::InterIMAP\\s+)[0-9.]+(\\s|\\$$)/ s//\\1$$VERS\\2/" \ -- ./interimap ./pullimap && \ git commit -m "Prepare new release v$$VERS." \ - -- ./interimap ./pullimap ./Changelog && \ + -- ./Changelog ./interimap ./pullimap ./lib/Net/IMAP/InterIMAP.pm && \ git tag -sm "Release version $$VERS" "v$$VERS" ## make html CSS="https://guilhem.org/static/css/bootstrap.min.css" BUILD_DOCDIR="$XDG_RUNTIME_DIR/Downloads" @@ -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-2020 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. diff --git a/debian/changelog b/debian/changelog index 4cd7cca..562d4dc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,24 @@ +interimap (0.5.5-1) unstable; urgency=medium + + * New upstream release. Highlights include: + + The 'SSL_protocols' option is now deprecated, and replaced by the + combination of 'SSL_protocol_min' and 'SSL_protocol_max'. + + Use default locations for trusted CA certificates (respectively + /etc/ssl/certs/ca-certificates.crt and /etc/ssl/certs on Debian systems) + when neither CAfile nor CApath are set. + + SSL/TLS connections now require OpenSSL 1.1.0 or later. + + New option SSL_ciphersuites to set the TLSv1.3 ciphersuites. + (SSL_cipherlist only apply to TLSv1.2 and below.) + * d/control: pin exact libinterimap version in interimap & pullimap's + depends. The components are tightly tied together and libinterimap makes + no promise of API stability. + * d/control: Bump dovecot-imapd version to >=2.3 in Build-Depends for the + test suite. + * d/.gitattributes: New file to merge d/changelog with + dpkg-mergechangelogs(1). + + -- Guilhem Moulin <guilhem@debian.org> Sat, 26 Dec 2020 23:57:56 +0100 + interimap (0.5.4-1~bpo10+1) buster-backports; urgency=medium * Rebuild for buster-backports. diff --git a/debian/control b/debian/control index f969966..2cad992 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: mail Priority: optional Maintainer: Guilhem Moulin <guilhem@debian.org> Build-Depends: debhelper-compat (= 13), - dovecot-imapd (>= 1:2.2.31~) <!nocheck>, + dovecot-imapd (>= 1:2.3~) <!nocheck>, dovecot-lmtpd (>= 1:2~) <!nocheck>, jq, libconfig-tiny-perl <!nocheck>, @@ -23,7 +23,7 @@ Vcs-Browser: https://salsa.debian.org/debian/interimap Package: interimap Architecture: all Depends: libdbd-sqlite3-perl, - libinterimap (>= ${source:Upstream-Version}~), + libinterimap (= ${binary:Version}), ${misc:Depends}, ${perl:Depends} Suggests: dovecot-imapd (>= 1:2~) @@ -48,9 +48,7 @@ Description: Fast bidirectional synchronization for QRESYNC-capable IMAP servers Package: pullimap Architecture: all -Depends: libinterimap (>= ${source:Upstream-Version}~), - ${misc:Depends}, - ${perl:Depends} +Depends: libinterimap (= ${binary:Version}), ${misc:Depends}, ${perl:Depends} Description: Pull mails from an IMAP mailbox and deliver them via SMTP or LMTP PullIMAP retrieves messages from an IMAP mailbox and deliver them to an SMTP or LMTP transmission channel. It can also remove old messages after diff --git a/debian/copyright b/debian/copyright index 12704ee..599b277 100644 --- a/debian/copyright +++ b/debian/copyright @@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Source: https://git.guilhem.org/interimap/ Files: * -Copyright: © 2015-2018 Guilhem Moulin <guilhem@fripost.org> +Copyright: © 2015-2020 Guilhem Moulin <guilhem@fripost.org> License: GPL-3+ License: GPL-3+ diff --git a/debian/gbp.conf b/debian/gbp.conf index ec669a5..aabb629 100644 --- a/debian/gbp.conf +++ b/debian/gbp.conf @@ -4,3 +4,6 @@ debian-branch = debian/buster-backports upstream-tag = v%(version)s debian-tag = debian/%(version)s pristine-tar = False + +[pq] +patch-numbers = False diff --git a/debian/interimap.docs b/debian/interimap.docs index bc0d414..724f6e5 100644 --- a/debian/interimap.docs +++ b/debian/interimap.docs @@ -1,5 +1,4 @@ share/doc/interimap/README share/doc/interimap/getting-started.md -share/doc/interimap/multi-account.md share/doc/interimap/interimap.sample -share/doc/interimap/getting-started.md +share/doc/interimap/multi-account.md diff --git a/debian/patches/Mention-the-Debian-BTS-in-the-manpages.patch b/debian/patches/Mention-the-Debian-BTS-in-the-manpages.patch index 89469c6..fefcf37 100644 --- a/debian/patches/Mention-the-Debian-BTS-in-the-manpages.patch +++ b/debian/patches/Mention-the-Debian-BTS-in-the-manpages.patch @@ -1,16 +1,18 @@ From: Guilhem Moulin <guilhem@debian.org> Date: Sun, 20 Jan 2019 21:09:43 +0100 Subject: Mention the Debian BTS in the manpages. -Forwarded: not-needed +Forwarded: not-needed --- - doc/interimap.1.md | 10 +++++++++- - doc/pullimap.1.md | 6 ++++++ + doc/interimap.1.md | 10 +++++++++- + doc/pullimap.1.md | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) +diff --git a/doc/interimap.1.md b/doc/interimap.1.md +index 2d588ae..03adbf5 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md -@@ -555,10 +555,18 @@ Standards +@@ -574,10 +574,18 @@ Standards and Quick Mailbox Resynchronization (`QRESYNC`)_, [RFC 7162], May 2014. @@ -30,9 +32,11 @@ Forwarded: not-needed [RFC 7162]: https://tools.ietf.org/html/rfc7162 [RFC 5258]: https://tools.ietf.org/html/rfc5258 +diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md +index 89969b2..900221a 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md -@@ -372,6 +372,12 @@ Standards +@@ -391,6 +391,12 @@ Standards * J. Klensin, _Simple Mail Transfer Protocol_, [RFC 5321], October 2008. diff --git a/debian/patches/Skip-randomized-tests.patch b/debian/patches/Skip-randomized-tests.patch index 837cbfc..14b4ba0 100644 --- a/debian/patches/Skip-randomized-tests.patch +++ b/debian/patches/Skip-randomized-tests.patch @@ -1,8 +1,6 @@ -commit 5bc242612a514d5091b1a05e5a087d54454e390a -Author: Guilhem Moulin <guilhem@debian.org> -Date: Wed Jan 29 13:49:10 2020 +0100 - -Avoid running randomized tests on the build daemons +From: Guilhem Moulin <guilhem@debian.org> +Date: Wed, 29 Jan 2020 13:49:10 +0100 +Subject: Avoid running randomized tests on the build daemons These tests are racy and can fail in subtle ways depending on the machine load and speed. Basically `interimap --watch=1` is spawnedin @@ -18,10 +16,13 @@ reached, then stop. These tests most sense in the context of upstream development, so for now we skip them in the Debian context. +Forwarded: not-needed --- - tests/list | 20 ++++++++++---------- + tests/list | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) +diff --git a/tests/list b/tests/list +index d1058ba..d6b3ec0 100644 --- a/tests/list +++ b/tests/list @@ -41,8 +41,8 @@ repair --repair @@ -35,9 +36,9 @@ now we skip them in the Debian context. . SSL/TLS starttls-logindisabled LOGINDISABLED STARTTLS -@@ -55,11 +55,11 @@ split-set Split large sets to avoid ex - tls-sni TLS servername extension (SNI) +@@ -56,11 +56,11 @@ split-set Split large sets to avoid extra-long command lines tls-protocols force TLS protocol versions + tls-ciphers force TLS cipher list/suites -. Live synchronization (60s) - sync-live local/remote simulation diff --git a/doc/getting-started.md b/doc/getting-started.md index 83d3ba9..74fc8da 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 diff --git a/doc/interimap.1.md b/doc/interimap.1.md index 2d2a637..2d588ae 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`. @@ -383,19 +383,32 @@ 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* @@ -415,12 +428,12 @@ Valid options are: Specifying multiple digest values can be useful in key rollover scenarios and/or when the server supports certificates of different - types (for instance RSA+ECDSA). In that case the connection is - aborted when none of the specified digests matches. + 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, and match its +: 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`.) @@ -430,6 +443,14 @@ Valid options are: 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 when @@ -437,10 +458,8 @@ Valid options are: This directory must be in “hash format”, see [`verify`(1ssl)] for more information. -*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_hostname* diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index c9500e0..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,7 +133,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 `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`.) @@ -202,19 +202,32 @@ 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* @@ -234,12 +247,12 @@ Valid options are: Specifying multiple digest values can be useful in key rollover scenarios and/or when the server supports certificates of different - types (for instance RSA+ECDSA). In that case the connection is - aborted when none of the specified digests matches. + 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, and match its +: 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`.) @@ -249,6 +262,14 @@ Valid options are: 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 when @@ -256,10 +277,8 @@ Valid options are: This directory must be in “hash format”, see [`verify`(1ssl)] for more information. -*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_hostname* @@ -322,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). @@ -2,7 +2,7 @@ #---------------------------------------------------------------------- # Fast bidirectional synchronization for QRESYNC-capable IMAP servers -# Copyright © 2015-2019 Guilhem Moulin <guilhem@fripost.org> +# Copyright © 2015-2020 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.14.2; use strict; use warnings; -our $VERSION = '0.5.4'; +our $VERSION = '0.5.5'; my $NAME = 'interimap'; my $DATABASE_VERSION = 1; use Getopt::Long qw/:config posix_default no_ignore_case gnu_compat @@ -32,7 +32,7 @@ use DBD::SQLite::Constants ':file_open'; use Fcntl qw/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 Net::IMAP::InterIMAP 0.5.5 qw/xdg_basedir read_config compact_set/; # Clean up PATH $ENV{PATH} = join ':', qw{/usr/bin /bin}; @@ -286,7 +286,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 b4d131c..36eeb2a 100644 --- a/interimap.sample +++ b/interimap.sample @@ -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/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index fff1570..0c4fc89 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-2020 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.5; use v5.20.0; use warnings; use strict; @@ -62,9 +62,12 @@ 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_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/, @@ -325,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) 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 = $!; @@ -345,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/) { @@ -360,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: $!"); } } @@ -1368,7 +1371,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; @@ -1490,9 +1493,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 @@ -1509,7 +1512,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; } @@ -1521,7 +1524,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; @@ -1579,7 +1582,7 @@ sub _proxify($$$$) { my ($err, $ipaddr) = getnameinfo($_->{addr}, NI_NUMERICHOST, NIx_NOSERV); $err eq '' ? [$ipaddr,$_->{family}] : undef } @res; - $self->fail("Can't getnameinfo") unless defined $addr; + $self->fail("getnameinfo") unless defined $addr; ($hostip, $fam) = @$addr; } @@ -1624,7 +1627,7 @@ sub _proxify($$$$) { # $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 @@ -1638,7 +1641,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'; @@ -1675,7 +1678,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; @@ -1688,51 +1691,84 @@ 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 $openssl_version = Net::SSLeay::OPENSSL_VERSION_NUMBER(); - 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()"); @@ -1742,15 +1778,16 @@ sub _start_ssl($$) { my $host = $self->{host} // $self->panic(); my ($hostip, $hostipfam) = _parse_hostip($host); - if ($self->{SSL_verify} // 1) { - # for X509_VERIFY_PARAM_set1_{ip,host}() - $self->panic("Failed requirement libssl >=1.0.2") if $openssl_version < 0x1000200f; - + if ($self->{SSL_verify}) { # verify 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"); + 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 @@ -1769,33 +1806,26 @@ sub _start_ssl($$) { 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("Can't create new SSL structure"); - Net::SSLeay::set_fd($ssl, fileno $socket) or $self->fail("SSL filehandle association failed"); + 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) on OpenSSL >=0.9.8f + # 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->panic("Failed requirement libssl >=0.9.8f") if $openssl_version < 0x00908070; - $self->_ssl_error("Can't set TLS servername extension (value $servername)") + $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}; } $self->_ssl_error("Can't initiate TLS/SSL handshake") unless Net::SSLeay::connect($ssl) == 1; $self->panic() unless $self->{_SSL_PEER_VERIFIED}; # sanity check - $self->panic() if ($self->{SSL_verify} // 1) and Net::SSLeay::get_verify_result($ssl) != Net::SSLeay::X509_V_OK(); + $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))); @@ -1830,7 +1860,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; @@ -2043,7 +2073,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-2020 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.4'; +our $VERSION = '0.5.5'; my $NAME = 'pullimap'; use Errno 'EINTR'; @@ -31,7 +31,7 @@ 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 Net::IMAP::InterIMAP 0.5.5 qw/xdg_basedir read_config compact_set/; # Clean up PATH $ENV{PATH} = join ':', qw{/usr/bin /bin}; @@ -104,7 +104,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 +117,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 +333,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/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..2b26451 100644 --- a/tests/snippets/dovecot/imapd.conf +++ b/tests/config/dovecot/imapd.conf 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..6aa8365 100644 --- a/tests/snippets/dovecot/lmtpd.conf +++ b/tests/config/dovecot/lmtpd.conf diff --git a/tests/snippets/dovecot/ssl.conf b/tests/config/dovecot/ssl.conf index 2d68c80..3fd99d5 100644 --- a/tests/snippets/dovecot/ssl.conf +++ b/tests/config/dovecot/ssl.conf @@ -2,3 +2,4 @@ ssl = required ssl_cert = <dovecot.rsa.crt ssl_key = <dovecot.rsa.key ssl_dh = <dhparams.pem +ssl_min_protocol = TLSv1 @@ -54,6 +54,7 @@ split-set Split large sets to avoid extra-long command lines 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 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..bc287dd 100644 --- a/tests/preauth-plaintext/t +++ b/tests/preauth-plaintext/t @@ -10,7 +10,7 @@ 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 @@ -35,6 +35,9 @@ if [ ! -d "$TESTDIR" ]; then exit 1 fi +# cleanup environment +unset OPENSSL_CONF SSL_CERT_FILE SSL_CERT_DIR + ROOTDIR="$(mktemp --tmpdir="${TMPDIR:-/dev/shm}" --directory "$1.XXXXXXXXXX")" declare -a DOVECOT_SERVER=() trap cleanup EXIT INT TERM @@ -101,7 +104,7 @@ 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)" @@ -207,6 +210,9 @@ _interimap_cmd() { local script="$1" rv=0 shift environ_set "local" + [ -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" ) env -i "${ENVIRON[@]}" perl -I./lib -T "./$script" "$@" 2>"$STDERR" || rv=$? cat <"$STDERR" >&2 return $rv diff --git a/tests/starttls-injection/imapd b/tests/starttls-injection/imapd index 15c53c7..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.rsa.key", &Net::SSLeay::FILETYPE_PEM) - or die_if_ssl_error("Can't load private key: $!"); + 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_if_ssl_error("Can't load certificate: $!"); + 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/t b/tests/starttls/t index 5f9bd4f..62b2151 100644 --- a/tests/starttls/t +++ b/tests/starttls/t @@ -21,9 +21,8 @@ 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\$$X509_SHA256" <"$STDERR" || 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" 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..0dfc771 --- /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=sh : diff --git a/tests/tls-pin-fingerprint/t b/tests/tls-pin-fingerprint/t index 6716833..883a887 100644 --- a/tests/tls-pin-fingerprint/t +++ b/tests/tls-pin-fingerprint/t @@ -41,8 +41,9 @@ EOF 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 # two invalid ones with_remote_config <<-EOF @@ -53,8 +54,9 @@ EOF 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 diff --git a/tests/tls-protocols/openssl.cnf b/tests/tls-protocols/openssl.cnf new file mode 100644 index 0000000..980097d --- /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 diff --git a/tests/tls-protocols/t b/tests/tls-protocols/t index f34a95b..72f7db2 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 : diff --git a/tests/tls-rsa+ecdsa/t b/tests/tls-rsa+ecdsa/t index 2adf930..c9f5b96 100644 --- a/tests/tls-rsa+ecdsa/t +++ b/tests/tls-rsa+ecdsa/t @@ -36,9 +36,11 @@ grep -Fx -e "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_S -e "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_ALT_SHA256" \ <"$STDERR" || error -# force RSA (XXX do we really have to force TLSv1.2 here?) +# 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_protocols = TLSv1.2 + SSL_protocol_max = TLSv1.2 SSL_cipherlist = EECDH+AESGCM+aRSA EOF interimap --debug || error diff --git a/tests/tls-verify-peer/t b/tests/tls-verify-peer/t index 2461a1f..8326521 100644 --- a/tests/tls-verify-peer/t +++ b/tests/tls-verify-peer/t @@ -15,8 +15,9 @@ unverified_peer() { [ -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 @@ -31,7 +32,7 @@ verified_peer() { [ -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" @@ -45,7 +46,9 @@ with_remote_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" @@ -53,13 +56,23 @@ with_remote_config <<-EOF SSL_fingerprint = sha256\$$PKEY_SHA256 EOF unverified_peer +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 + capath=$(mktemp --tmpdir="$TMPDIR" --directory capath.XXXXXX) cp -T -- ~/.dovecot/conf.d/ca.crt "$capath/ca-certificates.crt" -step_start "SSL_CAfile" +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 # assume our fake root CA is not there with_remote_config <<<"SSL_CAfile = /etc/ssl/certs/ca-certificates.crt" @@ -70,6 +83,10 @@ fi 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 @@ -79,7 +96,7 @@ for host in "ip6-localhost" "127.0.0.1" "::1"; do verified_peer done -# but not for other IPs or hostnames +# but not for other hostnames or IPs for host in "ip6-loopback" "127.0.1.1"; do with_remote_config <<-EOF host = $host @@ -91,7 +108,8 @@ done step_done -step_start "SSL_CApath" +step_start "SSL_CApath/\$SSL_CERT_DIR" + if [ -d "/etc/ssl/certs" ]; then # assume our fake root CA is not there with_remote_config <<<"SSL_CApath = /etc/ssl/certs" @@ -104,6 +122,10 @@ c_rehash "$capath" 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 diff --git a/tests/tls/t b/tests/tls/t index 9fdd399..a674b28 100644 --- a/tests/tls/t +++ b/tests/tls/t @@ -8,9 +8,8 @@ for ((i = 0; i < 32; i++)); do done interimap --debug || error -grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1" <"$STDERR" || error grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || 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" |