Package: ssmtp
Version: 2.64-8+b2
Severity: normal
Tags: patch upstream

During a TLS1.3 session, the client may see "post-handshake new session
tickets". The SSL_read() emulation done by GnuTLS returns a zero-byte
read for this case, and ssmtp thinks the session has been hung up.

For instance, on my machine (the server here is fairly stock qpsmtpd on
buster, but since this is a TLS feature presumably it's not uncommon):

  $ ssmtp -v p...@peff.net <msg 
  [<-] 220 peff.net ESMTP qpsmtpd 0.94 ready; send us your mail, but not your 
spam.
  [->] EHLO sigill.intra.peff.net
  [<-] 250 AUTH CRAM-MD5
  [->] STARTTLS
  [<-] 220 Go ahead with TLS
  [->] HELO sigill.intra.peff.net
  [<-] 
  ssmtp:  (sigill.intra.peff.net)

The client just hangs up the first time it tries to read any data after
setting up the TLS session, and no mail is sent.

If I connect with openssl, I see:

  $ openssl s_client -connect mail.intra.peff.net:587 -starttls smtp
  [...]
  ---
  New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
  [...]
  ---
  Post-Handshake New Session Ticket arrived:
  SSL-Session:
  Protocol  : TLSv1.3
  Cipher    : TLS_AES_256_GCM_SHA384
  [...]
  Post-Handshake New Session Ticket arrived:
  SSL-Session:
  Protocol  : TLSv1.3
  Cipher    : TLS_AES_256_GCM_SHA384
  [...]

So there are two re-ticketing events (which openssl handles just fine).
Curiously, if I replace the use of libgnutls-openssl in ssmtp with
actual libssl, then everything works fine. But I think gnutls can also
handle this case. What happens is that our SSL_read() call returns 0,
and ssmtp assumes that's an EOF and the session is done.

If I hack around it like this:

diff --git a/ssmtp.c b/ssmtp.c
index 7ab79ab..6b2b9d0 100644
--- a/ssmtp.c
+++ b/ssmtp.c
@@ -1291,8 +1291,12 @@ fd_getc() -- Read a character from an fd
 ssize_t fd_getc(int fd, void *c)
 {
 #ifdef HAVE_SSL
-       if(use_tls == True) { 
-               return(SSL_read(ssl, c, 1));
+       if(use_tls == True) {
+               int attempt = 3;
+               int ret = 0;
+               while (attempt-- > 0 && ret == 0)
+                       ret = SSL_read(ssl, c, 1);
+               return ret;
        }
 #endif
        return(read(fd, c, 1));

then we're able to skip past the re-ticketing and everything works as
expected.

It's not clear to me if the problem is in the gnutls-openssl wrappers,
or if ssmtp needs to be more careful about retrying reads. This is
somewhat similar to the ancient bug #312146, which was about checking
for SSL_ERROR_WANT_READ. But it is a bit different, in that ssmtp has no
way of knowing whether it's a real EOF or not. And empirically, real
openssl SSL_read() does not need any retries here (it's all handled
transparently).

So the patch above does work for me, but it's a pretty awful hack (and
it's not at all clear that 3 is enough; it just works for my case).


-- System Information:
Debian Release: bullseye/sid
  APT prefers unstable
  APT policy: (500, 'unstable'), (500, 'testing'), (1, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 4.19.0-5-amd64 (SMP w/8 CPU cores)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_WARN, TAINT_OOT_MODULE, 
TAINT_UNSIGNED_MODULE
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), 
LANGUAGE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)

Versions of packages ssmtp depends on:
ii  debconf [debconf-2.0]  1.5.72
ii  libc6                  2.28-10
ii  libgnutls-openssl27    3.6.8-2

ssmtp recommends no packages.

ssmtp suggests no packages.

-- debconf information excluded

Reply via email to