Document Jeremy's ref-count bug-fix as 4.89 JH/19
[exim.git] / src / src / receive.c
index f98eab18cde1af7d7d1b56d5516d2796405c8d6e..ed2c57ba5e75fc507a9ad3187c9a2d9334e121f5 100644 (file)
@@ -25,6 +25,7 @@ static FILE   *data_file = NULL;
 static int     data_fd = -1;
 static uschar *spool_name = US"";
 
+enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
 
 
 /*************************************************
@@ -905,7 +906,8 @@ a cut-down version of the state-machine above; we don't need to do leading-dot
 detection and unstuffing.
 
 Arguments:
-  fout      a FILE to which to write the message; NULL if skipping
+  fout      a FILE to which to write the message; NULL if skipping;
+            must be open for both writing and reading.
 
 Returns:    One of the END_xxx values indicating why it stopped reading
 */
@@ -913,27 +915,55 @@ Returns:    One of the END_xxx values indicating why it stopped reading
 static int
 read_message_bdat_smtp(FILE *fout)
 {
-int ch_state = 0, linelength = 0, ch;
+int linelength = 0, ch;
+enum CH_STATE ch_state = LF_SEEN;
+BOOL fix_nl = FALSE;
 
 for(;;)
   {
   switch ((ch = (bdat_getc)(GETC_BUFFER_UNLIMITED)))
     {
     case EOF:  return END_EOF;
-    case EOD:  return END_DOT;         /* normal exit */
     case ERR:  return END_PROTOCOL;
+    case EOD:
+      /* Nothing to get from the sender anymore. We check the last
+      character written to the spool.
+
+      RFC 3030 states, that BDAT chunks are normal text, terminated by CRLF.
+      If we would be strict, we would refuse such broken messages.
+      But we are liberal, so we fix it.  It would be easy just to append
+      the "\n" to the spool.
+
+      But there are some more things (line counting, message size calculation and such),
+      that would need to be duplicated here.  So we simply do some ungetc
+      trickery.
+      */
+      fseek(fout, -1, SEEK_CUR);
+      if (fgetc(fout) == '\n') return END_DOT;
+
+      if (linelength == -1)    /* \r already seen (see below) */
+        {
+        DEBUG(D_receive) debug_printf("Add missing LF\n");
+        bdat_ungetc('\n');
+        continue;
+        }
+      DEBUG(D_receive) debug_printf("Add missing CRLF\n");
+      bdat_ungetc('\r');      /* not even \r was seen */
+      fix_nl = TRUE;
+
+      continue;
     case '\0':  body_zerocount++; break;
     }
   switch (ch_state)
     {
-    case 0:                             /* After LF or CRLF */
-      ch_state = 1;
+    case LF_SEEN:                             /* After LF or CRLF */
+      ch_state = MID_LINE;
       /* fall through to handle as normal uschar. */
 
-    case 1:                             /* Mid-line state */
+    case MID_LINE:                            /* Mid-line state */
       if (ch == '\n')
        {
-       ch_state = 0;
+       ch_state = LF_SEEN;
        body_linecount++;
        if (linelength > max_received_linelength)
          max_received_linelength = linelength;
@@ -941,25 +971,26 @@ for(;;)
        }
       else if (ch == '\r')
        {
-       ch_state = 2;
+       ch_state = CR_SEEN;
+       if (fix_nl) bdat_ungetc('\n');
        continue;                       /* don't write CR */
        }
       break;
 
-    case 2:                             /* After (unwritten) CR */
+    case CR_SEEN:                       /* After (unwritten) CR */
       body_linecount++;
       if (linelength > max_received_linelength)
        max_received_linelength = linelength;
       linelength = -1;
       if (ch == '\n')
-       ch_state = 0;
+       ch_state = LF_SEEN;
       else
        {
        message_size++;
        if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
        (void) cutthrough_put_nl();
        if (ch == '\r') continue;       /* don't write CR */
-       ch_state = 1;
+       ch_state = MID_LINE;
        }
       break;
     }