This is based on behavior in the Debian cyrus-imapd-2.4 source package
version 2.4.12-2.  I have included a patch against that version.  By
code inspection, the same bug very probably still exists in master.  I
have also reported this in the Debian bug tracker:

    https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=747561

When using imtest with an SSL connection ("imtest -s ...") against an
Exchange IMAP server, long responses from the server sometimes cause
the program to hang after only partially printing the response, often
before printing a newline.  This makes using imtest with the Gnus
nnimap back end mostly useless against my Exchange server.  One such
long server response can result from running the "UID SEARCH ALL"
command on a mailbox containing thousands of messages.  A given
response text will not always consistently hang the program, which
implies a race condition.  Sending any input to imtest, e.g., a NOOP
command, will wake up imtest and cause it to print the remainder of
the response.  Unsolicited responses from the server, such as an
inactivity timeout, can also cause the remainder of the response to be
printed.

Using the "-v" flag to imtest shows that prior to the hang, a read
returns well over 4k bytes, but the printed output corresponding to
that read prints exactly 4096 bytes before hanging.  The cause is
imtest/imtest.c:interactive() inappropriately using the condition
(pin->cnt > 0) to determine whether to continue looping calling
lib/prot.c:prot_read().

The buffer that prot_read() uses to read from SSL_read() is exactly
4096 bytes long (PROT_BUFSIZE).  When an SSL record with a plaintext
larger than 4096 bytes arrives, repeated calls to prot_read() by
interactive() will eventually lead to pin->cnt (the protstream handle
for the input from the IMAP socket) reaching zero while data remains
to be read from the SSL buffers.  Further calls to prot_read() will
actually not block in this condition, but the readable condition is
possibly already cleared on the socket file descriptor, depending on
timing races with other output from the server.  If the socket
readable condition is cleared when pin->cnt reaches zero, the program
blocks in select() until there is some user input or an unsolicited
server response.  It is possible that the Exchange server needs to
have a very large MTU configured for this bug to trigger.

The fix is to call SSL_pending() when pin->cnt reaches zero to
determine if there really is no more input to read.  An
architecturally cleaner fix might be possible, but I imagine that
involves rewriting interactive() to work with prot_select().

--- a/imtest/imtest.c
+++ b/imtest/imtest.c
@@ -1205,6 +1205,24 @@
     gotsigint = 1;
 }
 
+/* If SSL_read() reads a record with more than PROT_BUFSIZE plaintext
+ * bytes, s->cnt can reach 0 even though more data is available.
+ * Check SSL_pending() in that case, otherwise more data might be
+ * available to SSL_read() but the read state could still be cleared
+ * on the file descriptor. */
+static int remaining(struct protstream *s)
+{
+#ifdef HAVE_SSL
+    if (s->cnt == 0 && s->tls_conn != NULL) {
+	int n = SSL_pending(s->tls_conn);
+	if (verbose)
+	    printf("SSL_pending=%d\n", n);
+	return n;
+    }
+#endif
+    return s->cnt;
+}
+
 /* This needs to support 3 modes:
  *
  * 1. Terminal Interface Only
@@ -1369,7 +1387,7 @@
 		    buf[count] = '\0';
 		    printf("%s", buf); 
 		}
-	    } while (pin->cnt > 0);
+	    } while (remaining(pin) > 0);
 	} else if ((FD_ISSET(fd, &rset)) && (FD_ISSET(sock, &wset))
 		   && (donewritingfile == 0)) {
 	    /* This does input for both socket and file modes */

Reply via email to