Yesterday one of our users was being bombarded with multiple copies of the same message. It had been sent to their departmental address, which is hosted on an Exchange 2003 SP2 server and which forwards a copy to an account at an external email service provider. The Exchange server received one copy of the message but was repeatedly retransmitting the forwarded copy. My Exim servers seemed to be accepting them OK, but for some reason the Exchange server thought something had gone wrong so kept retrying.
After looking at the logs and tcpdumping a few SMTP sessions, it became clear that Exchange was not dot-stuffing the message correctly. In SMTP a message is terminated by a single dot on a line; in order to allow messages to contain bare dots, any lines in the message that start with a dot have an extra dot inserted. This particular message had a bare dot on a line and Exchange was failing to stuff another one in front of it.
The effect of this was that when my Exim servers saw the dot, they accepted the message for delivery then expected to receive further commands. Instead they received further message content which could not be interpreted as commands, so they dropped the connection. The Exchange server just saw that the connection was dropped in the middle of message transmission, and didn't notice that my servers had accepted anything, so queued the message for a later delivery attempt.
(I usually suspect firewalls in cases like this, but the department in question only has a dumb firewall and the problem dot was nowhere near a packet boundary.)
It was clear that the message had been reformatted in the process of being forwarded since the message headers had been mangled. The problem dot was a full stop at the end of a paragraph which had been re-folded using MIME quoted-printable encoding. Q-P format allows very long lines to be broken using an =<CRLF> sequence, which is supposed to allow more reliable transmission of messages through the kind of weird non-Internet email systems that were prevalent in the early 1990s. Modern graphical email software tends to format paragraphs as a single very long line, and transmit them using Q-P line breaking.
I managed to get a pristine copy of the problem message (the sender re-sent it for some reason, and it was captured by my tcpdumps) and reduce it to a minimal test case which appears below. It requires:
- At least the MIME structure below, with alternate plain text and HTML parts, and a related part containing an image used by the HTML. (The original message also had another attachment containing a PDF.) It also works if the nesting of the multipaer/related and multipart/alternative parts is reversed.
- The alt attribute on the image must be present but must be the empty string.
- The problem paragraph must have a multiple of 76 octets plus a dot, so that when Exchange reformats the paragraph the dot ends up on a line by itself. (Some slop is allowed if a Q-P encoded character occurs near a 76-octet boundary because then Exchange doesn't break the line exactly on the 76 octet boundary. Note that one Q-P encoded character counts as 3 octets per un-encoded byte.)
- There must be no dots at the start of any lines.
This small message is enough to trigger the dot stuffing bug, but not quite enough to trigger the repeated retransmission, because there is enough buffering in the network for Exchange to send the whole message and listen for the response before Exim kills the connection. If the image is much larger Exim can accept the truncated message and kill the connection while Exchange is still transmitting the message.
I have tested this on an Exchange 2007 server which does not exhibit the problem. There is an article about this bug on Microsoft's web site, with a link to a "hot fix".
MIME-Version: 1.0 Content-Type: multipart/related; boundary="s1" --s1 Content-Type: multipart/alternative; boundary="s2" --s2 Content-Type: text/plain; Content-Transfer-Encoding: quoted-printable ____:____1____:____2____:____3____:___= ____:____1____:____2____:____3____:___. --s2 Content-Type: text/html Content-Transfer-Encoding: quoted-printable <HTML><BODY><P><IMG alt=3D"" src=3D"cid:193977609"></P></BODY></HTML> --s2-- --s1 Content-Type: image/gif Content-Transfer-Encoding: base64 Content-ID: <193977609> R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs= --s1--