CHUNKING: flush input stream after message-fatal error detection. Bug 2201
[exim.git] / src / src / receive.c
index 33c60e08db71c88aad40e104fd050ee872c3b5f9..1f1954c575460828b2d2e704e04bbb6b0e3ea060 100644 (file)
@@ -84,12 +84,10 @@ receive_check_set_sender(uschar *newsender)
 {
 uschar *qnewsender;
 if (trusted_caller) return TRUE;
-if (newsender == NULL || untrusted_set_sender == NULL) return FALSE;
-qnewsender = (Ustrchr(newsender, '@') != NULL)?
-  newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
-return
-  match_address_list(qnewsender, TRUE, TRUE, CUSS &untrusted_set_sender, NULL, -1,
-    0, NULL) == OK;
+if (!newsender || !untrusted_set_sender) return FALSE;
+qnewsender = Ustrchr(newsender, '@')
+  ? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
+return match_address_list_basic(qnewsender, CUSS &untrusted_set_sender, 0) == OK;
 }
 
 
@@ -192,7 +190,7 @@ if (STATVFS(CS path, &statbuf) != 0)
     log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
       "%s directory %s: %s", name, path, strerror(errno));
     smtp_closedown(US"spool or log directory problem");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, NULL);
     }
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
@@ -345,7 +343,7 @@ if (!already_bombing_out)
 
 /* Exit from the program (non-BSMTP cases) */
 
-exim_exit(EXIT_FAILURE);
+exim_exit(EXIT_FAILURE, NULL);
 }
 
 
@@ -831,7 +829,7 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
       {
       message_size++;
       if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-      (void) cutthrough_data_put_nl();
+      cutthrough_data_put_nl();
       if (ch != '\r') ch_state = 1; else continue;
       }
     break;
@@ -850,7 +848,7 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
     if (ch == '.')
       {
       uschar c= ch;
-      (void) cutthrough_data_puts(&c, 1);
+      cutthrough_data_puts(&c, 1);
       }
     ch_state = 1;
     break;
@@ -860,7 +858,7 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
     message_size++;
     body_linecount++;
     if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-    (void) cutthrough_data_put_nl();
+    cutthrough_data_put_nl();
     if (ch == '\r')
       {
       ch_state = 2;
@@ -881,11 +879,11 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
     if (message_size > thismessage_size_limit) return END_SIZE;
     }
   if(ch == '\n')
-    (void) cutthrough_data_put_nl();
+    cutthrough_data_put_nl();
   else
     {
     uschar c = ch;
-    (void) cutthrough_data_puts(&c, 1);
+    cutthrough_data_puts(&c, 1);
     }
   }
 
@@ -921,7 +919,7 @@ BOOL fix_nl = FALSE;
 
 for(;;)
   {
-  switch ((ch = (bdat_getc)(GETC_BUFFER_UNLIMITED)))
+  switch ((ch = bdat_getc(GETC_BUFFER_UNLIMITED)))
     {
     case EOF:  return END_EOF;
     case ERR:  return END_PROTOCOL;
@@ -991,7 +989,7 @@ for(;;)
        {
        message_size++;
        if (fout && fputc('\n', fout) == EOF) return END_WERROR;
-       (void) cutthrough_data_put_nl();
+       cutthrough_data_put_nl();
        if (ch == '\r') continue;       /* don't write CR */
        ch_state = MID_LINE;
        }
@@ -1008,16 +1006,59 @@ for(;;)
     if (message_size > thismessage_size_limit) return END_SIZE;
     }
   if(ch == '\n')
-    (void) cutthrough_data_put_nl();
+    cutthrough_data_put_nl();
   else
     {
     uschar c = ch;
-    (void) cutthrough_data_puts(&c, 1);
+    cutthrough_data_puts(&c, 1);
     }
   }
 /*NOTREACHED*/
 }
 
+static int
+read_message_bdat_smtp_wire(FILE *fout)
+{
+int ch;
+
+/* Remember that this message uses wireformat. */
+
+DEBUG(D_receive) debug_printf("CHUNKING: %s\n",
+       fout ? "writing spoolfile in wire format" : "flushing input");
+spool_file_wireformat = TRUE;
+
+for (;;)
+  {
+  if (chunking_data_left > 0)
+    {
+    unsigned len = MAX(chunking_data_left, thismessage_size_limit - message_size + 1);
+    uschar * buf = bdat_getbuf(&len);
+
+    message_size += len;
+    if (fout && fwrite(buf, len, 1, fout) != 1) return END_WERROR;
+    }
+  else switch (ch = bdat_getc(GETC_BUFFER_UNLIMITED))
+    {
+    case EOF: return END_EOF;
+    case EOD: return END_DOT;
+    case ERR: return END_PROTOCOL;
+
+    default:
+      message_size++;
+  /*XXX not done:
+  linelength
+  max_received_linelength
+  body_linecount
+  body_zerocount
+  */
+      if (fout && fputc(ch, fout) == EOF) return END_WERROR;
+      break;
+    }
+  if (message_size > thismessage_size_limit) return END_SIZE;
+  }
+/*NOTREACHED*/
+}
+
 
 
 
@@ -1037,9 +1078,10 @@ Returns:     nothing
 void
 receive_swallow_smtp(void)
 {
-/*XXX CHUNKING: not enough.  read chunks until RSET? */
 if (message_ended >= END_NOTENDED)
-  message_ended = read_message_data_smtp(NULL);
+  message_ended = chunking_state <= CHUNKING_OFFERED
+     ? read_message_data_smtp(NULL)
+     : read_message_bdat_smtp_wire(NULL);
 }
 
 
@@ -1102,7 +1144,7 @@ if (error_handling == ERRORS_SENDER)
 else
   fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
 (void)fclose(f);
-exim_exit(error_rc);
+exim_exit(error_rc, US"");
 }
 
 
@@ -1251,31 +1293,34 @@ the calling host to a string that is being built dynamically.
 
 Arguments:
   s           the dynamic string
-  sizeptr     points to the size variable
-  ptrptr      points to the pointer variable
 
 Returns:      the extended string
 */
 
-static uschar *
-add_host_info_for_log(uschar * s, int * sizeptr, int * ptrptr)
+static gstring *
+add_host_info_for_log(gstring * g)
 {
 if (sender_fullhost)
   {
   if (LOGGING(dnssec) && sender_host_dnssec)   /*XXX sender_helo_dnssec? */
-    s = string_cat(s, sizeptr, ptrptr, US" DS");
-  s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
+    g = string_catn(g, US" DS", 3);
+  g = string_append(g, 2, US" H=", sender_fullhost);
   if (LOGGING(incoming_interface) && interface_address != NULL)
     {
-    s = string_cat(s, sizeptr, ptrptr,
+    g = string_cat(g,
       string_sprintf(" I=[%s]:%d", interface_address, interface_port));
     }
   }
-if (sender_ident != NULL)
-  s = string_append(s, sizeptr, ptrptr, 2, US" U=", sender_ident);
-if (received_protocol != NULL)
-  s = string_append(s, sizeptr, ptrptr, 2, US" P=", received_protocol);
-return s;
+if (tcp_in_fastopen && !tcp_in_fastopen_logged)
+  {
+  g = string_catn(g, US" TFO", 4);
+  tcp_in_fastopen_logged = TRUE;
+  }
+if (sender_ident)
+  g = string_append(g, 2, US" U=", sender_ident);
+if (received_protocol)
+  g = string_append(g, 2, US" P=", received_protocol);
+return g;
 }
 
 
@@ -1308,36 +1353,30 @@ unsigned long mbox_size;
 header_line *my_headerlist;
 uschar *user_msg, *log_msg;
 int mime_part_count_buffer = -1;
+uschar * mbox_filename;
 int rc = OK;
 
 memset(CS rfc822_file_path,0,2048);
 
 /* check if it is a MIME message */
-my_headerlist = header_list;
-while (my_headerlist != NULL)
-  {
-  /* skip deleted headers */
-  if (my_headerlist->type == '*')
-    {
-    my_headerlist = my_headerlist->next;
-    continue;
-    }
-  if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0)
+
+for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next)
+  if (  my_headerlist->type != '*'                     /* skip deleted headers */
+     && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
+     )
     {
     DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
     goto DO_MIME_ACL;
     }
-  my_headerlist = my_headerlist->next;
-  }
 
 DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
 return TRUE;
 
 DO_MIME_ACL:
+
 /* make sure the eml mbox file is spooled up */
-mbox_file = spool_mbox(&mbox_size, NULL);
-if (mbox_file == NULL) {
-  /* error while spooling */
+if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename)))
+  {                                                            /* error while spooling */
   log_write(0, LOG_MAIN|LOG_PANIC,
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
@@ -1349,7 +1388,7 @@ if (mbox_file == NULL) {
   message_id[0] = 0;            /* Indicate no message accepted */
   *smtp_reply_ptr = US"";       /* Indicate reply already sent */
   return FALSE;                 /* Indicate skip to end of receive function */
-};
+  }
 
 mime_is_rfc822 = 0;
 
@@ -1373,14 +1412,13 @@ if (Ustrlen(rfc822_file_path) > 0)
 /* check if we must check any message/rfc822 attachments */
 if (rc == OK)
   {
-  uschar temp_path[1024];
+  uschar * scandir;
   struct dirent * entry;
   DIR * tempdir;
 
-  (void) string_format(temp_path, sizeof(temp_path), "%s/scan/%s",
-    spool_directory, message_id);
+  scandir = string_copyn(mbox_filename, Ustrrchr(mbox_filename, '/') - mbox_filename);
 
-  tempdir = opendir(CS temp_path);
+  tempdir = opendir(CS scandir);
   for (;;)
     {
     if (!(entry = readdir(tempdir)))
@@ -1388,7 +1426,7 @@ if (rc == OK)
     if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
       (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
-       "%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
+       "%s/%s", scandir, entry->d_name);
       DEBUG(D_receive) debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
        rfc822_file_path);
       break;
@@ -1587,7 +1625,7 @@ int  process_info_len = Ustrlen(process_info);
 int  error_rc = (error_handling == ERRORS_SENDER)?
        errors_sender_rc : EXIT_FAILURE;
 int  header_size = 256;
-int  start, end, domain, size, sptr;
+int  start, end, domain;
 int  id_resolution;
 int  had_zero = 0;
 int  prevlines_length = 0;
@@ -1612,7 +1650,8 @@ error_block *bad_addresses = NULL;
 uschar *frozen_by = NULL;
 uschar *queued_by = NULL;
 
-uschar *errmsg, *s;
+uschar *errmsg;
+gstring * g;
 struct stat statbuf;
 
 /* Final message to give to SMTP caller, and messages from ACLs */
@@ -1710,9 +1749,9 @@ message id creation below. */
 
 /* For other uses of the received time we can operate with granularity of one
 second, and for that we use the global variable received_time. This is for
-things like ultimate message timeouts. */
+things like ultimate message timeouts.XXX */
 
-received_time = message_id_tv.tv_sec;
+received_time = message_id_tv;
 
 /* If SMTP input, set the special handler for timeouts. The alarm() calls
 happen in the smtp_getc() function when it refills its buffer. */
@@ -1773,8 +1812,8 @@ for (;;)
   (and sometimes lunatic messages can have ones that are 100s of K long) we
   call store_release() for strings that have been copied - if the string is at
   the start of a block (and therefore the only thing in it, because we aren't
-  doing any other gets), the block gets freed. We can only do this because we
-  know there are no other calls to store_get() going on. */
+  doing any other gets), the block gets freed. We can only do this release if
+  there were no allocations since the once that we want to free. */
 
   if (ptr >= header_size - 4)
     {
@@ -1783,9 +1822,10 @@ for (;;)
     header_size *= 2;
     if (!store_extend(next->text, oldsize, header_size))
       {
+      BOOL release_ok = store_last_get[store_pool] == next->text;
       uschar *newtext = store_get(header_size);
       memcpy(newtext, next->text, ptr);
-      store_release(next->text);
+      if (release_ok) store_release(next->text);
       next->text = newtext;
       }
     }
@@ -1827,7 +1867,7 @@ for (;;)
   prevent further reading), and break out of the loop, having freed the
   empty header, and set next = NULL to indicate no data line. */
 
-  if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
+  if (ptr == 0 && ch == '.' && dot_ends)
     {
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\r')
@@ -2014,32 +2054,30 @@ for (;;)
   these lines in SMTP messages. There is now an option to ignore them from
   specified hosts or networks. Sigh. */
 
-  if (header_last == header_list &&
-       (!smtp_input
-         ||
-         (sender_host_address != NULL &&
-           verify_check_host(&ignore_fromline_hosts) == OK)
-         ||
-         (sender_host_address == NULL && ignore_fromline_local)
-       ) &&
-       regex_match_and_setup(regex_From, next->text, 0, -1))
+  if (  header_last == header_list
+     && (  !smtp_input
+        || (  sender_host_address
+          && verify_check_host(&ignore_fromline_hosts) == OK
+          )
+        || (!sender_host_address && ignore_fromline_local)
+        )
+     && regex_match_and_setup(regex_From, next->text, 0, -1)
+     )
     {
     if (!sender_address_forced)
       {
       uschar *uucp_sender = expand_string(uucp_from_sender);
-      if (uucp_sender == NULL)
-        {
+      if (!uucp_sender)
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" failed after matching "
           "\"From \" line: %s", uucp_from_sender, expand_string_message);
-        }
       else
         {
         int start, end, domain;
         uschar *errmess;
         uschar *newsender = parse_extract_address(uucp_sender, &errmess,
           &start, &end, &domain, TRUE);
-        if (newsender != NULL)
+        if (newsender)
           {
           if (domain == 0 && newsender[0] != 0)
             newsender = rewrite_address_qualify(newsender, FALSE);
@@ -2134,13 +2172,11 @@ for (;;)
         }
 
       else
-        {
         give_local_error(ERRMESS_VLONGHDRLINE,
           string_sprintf("message header line longer than %d characters "
            "received: message not accepted", header_line_maxsize), US"",
            error_rc, stdin, header_list->next);
         /* Does not return */
-        }
       }
 
     /* Note if any resent- fields exist. */
@@ -2161,7 +2197,7 @@ for (;;)
       sender_address,
       sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
       sender_ident ? " U=" : "",    sender_ident ? sender_ident : US"");
-    smtp_printf("552 Message header not CRLF terminated\r\n");
+    smtp_printf("552 Message header not CRLF terminated\r\n", FALSE);
     bdat_flush_data();
     smtp_reply = US"";
     goto TIDYUP;                             /* Skip to end of function */
@@ -2583,8 +2619,9 @@ letter and it is not used internally.
 NOTE: If ever the format of message ids is changed, the regular expression for
 checking that a string is in this format must be updated in a corresponding
 way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH
-must also be changed to reflect the correct string length. Then, of course,
-other programs that rely on the message id format will need updating too. */
+must also be changed to reflect the correct string length. The queue-sort code
+needs to know the layout. Then, of course, other programs that rely on the
+message id format will need updating too. */
 
 Ustrncpy(message_id, string_base62((long int)(message_id_tv.tv_sec)), 6);
 message_id[6] = '-';
@@ -2994,7 +3031,6 @@ if (chunking_state > CHUNKING_OFFERED)
 /* Cutthrough delivery:
 We have to create the Received header now rather than at the end of reception,
 so the timestamp behaviour is a change to the normal case.
-XXX Ensure this gets documented XXX.
 Having created it, send the headers to the destination. */
 
 if (cutthrough.fd >= 0 && cutthrough.delivery)
@@ -3087,9 +3123,11 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
-    message_ended = chunking_state > CHUNKING_OFFERED
-      ? read_message_bdat_smtp(data_file)
-      : read_message_data_smtp(data_file);
+    message_ended = chunking_state <= CHUNKING_OFFERED
+      ? read_message_data_smtp(data_file)
+      : spool_wireformat
+      ? read_message_bdat_smtp_wire(data_file)
+      : read_message_bdat_smtp(data_file);
     receive_linecount++;                /* The terminating "." line */
     }
   else message_ended = read_message_data(data_file);
@@ -3285,7 +3323,7 @@ if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
     {
     Uunlink(spool_name);
     (void)fclose(data_file);
-    exim_exit(error_rc);
+    exim_exit(error_rc, US"receiving");
     }
   }
 
@@ -3349,103 +3387,96 @@ else
 #ifndef DISABLE_DKIM
     if (!dkim_disable_verify)
       {
-      /* Finish verification, this will log individual signature results to
-         the mainlog */
+      /* Finish verification */
       dkim_exim_verify_finish();
 
       /* Check if we must run the DKIM ACL */
       if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
         {
-        uschar *dkim_verify_signers_expanded =
+        uschar * dkim_verify_signers_expanded =
           expand_string(dkim_verify_signers);
-        if (!dkim_verify_signers_expanded)
+       gstring * results = NULL;
+       int signer_sep = 0;
+       const uschar * ptr;
+       uschar * item;
+       gstring * seen_items = NULL;
+       int old_pool = store_pool;
+
+       store_pool = POOL_PERM;   /* Allow created variables to live to data ACL */
+
+        if (!(ptr = dkim_verify_signers_expanded))
           log_write(0, LOG_MAIN|LOG_PANIC,
             "expansion of dkim_verify_signers option failed: %s",
             expand_string_message);
 
-        else
-          {
-          int sep = 0;
-          const uschar *ptr = dkim_verify_signers_expanded;
-          uschar *item = NULL;
-          uschar *seen_items = NULL;
-          int     seen_items_size = 0;
-          int     seen_items_offset = 0;
-          /* Default to OK when no items are present */
-          rc = OK;
-          while ((item = string_nextinlist(&ptr, &sep, NULL, 0)))
-            {
-            /* Prevent running ACL for an empty item */
-            if (!item || !*item) continue;
-
-            /* Only run ACL once for each domain or identity,
-           no matter how often it appears in the expanded list. */
-            if (seen_items)
-              {
-              uschar *seen_item = NULL;
-              const uschar *seen_items_list = seen_items;
-              BOOL seen_this_item = FALSE;
-
-              while ((seen_item = string_nextinlist(&seen_items_list, &sep,
-                                                    NULL, 0)))
-               if (Ustrcmp(seen_item,item) == 0)
-                 {
-                 seen_this_item = TRUE;
-                 break;
-                 }
-
-              if (seen_this_item)
-                {
-                DEBUG(D_receive)
-                  debug_printf("acl_smtp_dkim: skipping signer %s, "
-                   "already seen\n", item);
-                continue;
-                }
-
-              seen_items = string_append(seen_items, &seen_items_size,
-               &seen_items_offset, 1, ":");
-              }
-
-            seen_items = string_append(seen_items, &seen_items_size,
-             &seen_items_offset, 1, item);
-            seen_items[seen_items_offset] = '\0';
-
-            DEBUG(D_receive)
-              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n",
-               item);
-
-            dkim_exim_acl_setup(item);
-            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
-                 &user_msg, &log_msg);
-
-            if (rc != OK)
+       /* Default to OK when no items are present */
+       rc = OK;
+       while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0)))
+         {
+         /* Prevent running ACL for an empty item */
+         if (!item || !*item) continue;
+
+         /* Only run ACL once for each domain or identity,
+         no matter how often it appears in the expanded list. */
+         if (seen_items)
+           {
+           uschar * seen_item;
+           const uschar * seen_items_list = string_from_gstring(seen_items);
+           int seen_sep = ':';
+           BOOL seen_this_item = FALSE;
+
+           while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
+                                                 NULL, 0)))
+             if (Ustrcmp(seen_item,item) == 0)
+               {
+               seen_this_item = TRUE;
+               break;
+               }
+
+           if (seen_this_item)
              {
              DEBUG(D_receive)
-               debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
-                 "skipping remaining items\n", rc, item);
-             cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
-             break;
+               debug_printf("acl_smtp_dkim: skipping signer %s, "
+                 "already seen\n", item);
+             continue;
              }
-            }
-          add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
-          if (rc == DISCARD)
-            {
-            recipients_count = 0;
-            blackholed_by = US"DKIM ACL";
-            if (log_msg != NULL)
-              blackhole_log_msg = string_sprintf(": %s", log_msg);
-            }
-          else if (rc != OK)
-            {
-            Uunlink(spool_name);
-            if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
-              smtp_yield = FALSE;    /* No more messages after dropped connection */
-            smtp_reply = US"";       /* Indicate reply already sent */
-            message_id[0] = 0;       /* Indicate no message accepted */
-            goto TIDYUP;             /* Skip to end of function */
-            }
-          }
+
+           seen_items = string_catn(seen_items, US":", 1);
+           }
+         seen_items = string_cat(seen_items, item);
+
+         rc = dkim_exim_acl_run(item, &results, &user_msg, &log_msg);
+         if (rc != OK)
+           {
+           DEBUG(D_receive)
+             debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+               "skipping remaining items\n", rc, item);
+           cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
+           break;
+           }
+         }
+       dkim_verify_status = string_from_gstring(results);
+       store_pool = old_pool;
+       add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
+       if (rc == DISCARD)
+         {
+         recipients_count = 0;
+         blackholed_by = US"DKIM ACL";
+         if (log_msg)
+           blackhole_log_msg = string_sprintf(": %s", log_msg);
+         }
+       else if (rc != OK)
+         {
+         Uunlink(spool_name);
+         if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+           smtp_yield = FALSE;    /* No more messages after dropped connection */
+         smtp_reply = US"";       /* Indicate reply already sent */
+         message_id[0] = 0;       /* Indicate no message accepted */
+         goto TIDYUP;             /* Skip to end of function */
+         }
         }
+      else
+       dkim_exim_verify_log_all();
       }
 #endif /* DISABLE_DKIM */
 
@@ -3467,7 +3498,7 @@ else
       int all_pass = OK;
       int all_fail = FAIL;
 
-      smtp_printf("353 PRDR content analysis beginning\r\n");
+      smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
       /* Loop through recipients, responses must be in same order received */
       for (c = 0; recipients_count > c; c++)
         {
@@ -3643,6 +3674,7 @@ dcc_ok = 0;
 version supplied with Exim always accepts, but this is a hook for sysadmins to
 supply their own checking code. The local_scan() function is run even when all
 the recipients have been discarded. */
+/*XXS could we avoid this for the standard case, given that few people will use it? */
 
 lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
 
@@ -3732,10 +3764,8 @@ multiline SMTP responses. */
 else
   {
   uschar *istemp = US"";
-  uschar *s = NULL;
   uschar *smtp_code;
-  int size = 0;
-  int sptr = 0;
+  gstring * g;
 
   errmsg = local_scan_data;
 
@@ -3743,38 +3773,37 @@ else
   switch(rc)
     {
     default:
-    log_write(0, LOG_MAIN, "invalid return %d from local_scan(). Temporary "
-      "rejection given", rc);
-    goto TEMPREJECT;
+      log_write(0, LOG_MAIN, "invalid return %d from local_scan(). Temporary "
+       "rejection given", rc);
+      goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
-    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
-    /* Fall through */
+      BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+      /* Fall through */
 
     case LOCAL_SCAN_REJECT:
-    smtp_code = US"550";
-    if (errmsg == NULL) errmsg =  US"Administrative prohibition";
-    break;
+      smtp_code = US"550";
+      if (!errmsg) errmsg =  US"Administrative prohibition";
+      break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
-    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
-    /* Fall through */
+      BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+      /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
     TEMPREJECT:
-    smtp_code = US"451";
-    if (errmsg == NULL) errmsg = US"Temporary local problem";
-    istemp = US"temporarily ";
-    break;
+      smtp_code = US"451";
+      if (!errmsg) errmsg = US"Temporary local problem";
+      istemp = US"temporarily ";
+      break;
     }
 
-  s = string_append(s, &size, &sptr, 2, US"F=",
-    (sender_address[0] == 0)? US"<>" : sender_address);
-  s = add_host_info_for_log(s, &size, &sptr);
-  s[sptr] = 0;
+  g = string_append(NULL, 2, US"F=",
+    sender_address[0] == 0 ? US"<>" : sender_address);
+  g = add_host_info_for_log(g);
 
   log_write(0, LOG_MAIN|LOG_REJECT, "%s %srejected by local_scan(): %.256s",
-    s, istemp, string_printing(errmsg));
+    string_from_gstring(g), istemp, string_printing(errmsg));
 
   if (smtp_input)
     {
@@ -3908,58 +3937,53 @@ it first! Include any message id that is in the message - since the syntax of a
 message id is actually an addr-spec, we can use the parse routine to canonicalize
 it. */
 
-size = 256;
-sptr = 0;
-s = store_get(size);
+g = string_get(256);
 
-s = string_append(s, &size, &sptr, 2,
+g = string_append(g, 2,
   fake_response == FAIL ? US"(= " : US"<= ",
   sender_address[0] == 0 ? US"<>" : sender_address);
 if (message_reference)
-  s = string_append(s, &size, &sptr, 2, US" R=", message_reference);
+  g = string_append(g, 2, US" R=", message_reference);
 
-s = add_host_info_for_log(s, &size, &sptr);
+g = add_host_info_for_log(g);
 
 #ifdef SUPPORT_TLS
 if (LOGGING(tls_cipher) && tls_in.cipher)
-  s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
+  g = string_append(g, 2, US" X=", tls_in.cipher);
 if (LOGGING(tls_certificate_verified) && tls_in.cipher)
-  s = string_append(s, &size, &sptr, 2, US" CV=",
-    tls_in.certificate_verified ? "yes":"no");
+  g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no");
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
-  s = string_append(s, &size, &sptr, 3, US" DN=\"",
-    string_printing(tls_in.peerdn), US"\"");
+  g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
-  s = string_append(s, &size, &sptr, 3, US" SNI=\"",
-    string_printing(tls_in.sni), US"\"");
+  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
 #endif
 
 if (sender_host_authenticated)
   {
-  s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
+  g = string_append(g, 2, US" A=", sender_host_authenticated);
   if (authenticated_id)
     {
-    s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
+    g = string_append(g, 2, US":", authenticated_id);
     if (LOGGING(smtp_mailauth) && authenticated_sender)
-      s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
+      g = string_append(g, 2, US":", authenticated_sender);
     }
   }
 
 #ifndef DISABLE_PRDR
 if (prdr_requested)
-  s = string_catn(s, &size, &sptr, US" PRDR", 5);
+  g = string_catn(g, US" PRDR", 5);
 #endif
 
 #ifdef SUPPORT_PROXY
 if (proxy_session && LOGGING(proxy))
-  s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
+  g = string_append(g, 2, US" PRX=", proxy_local_address);
 #endif
 
 if (chunking_state > CHUNKING_OFFERED)
-  s = string_catn(s, &size, &sptr, US" K", 2);
+  g = string_catn(g, US" K", 2);
 
 sprintf(CS big_buffer, "%d", msg_size);
-s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
+g = string_append(g, 2, US" S=", big_buffer);
 
 /* log 8BITMIME mode announced in MAIL_FROM
    0 ... no BODY= used
@@ -3968,11 +3992,11 @@ s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
 if (LOGGING(8bitmime))
   {
   sprintf(CS big_buffer, "%d", body_8bitmime);
-  s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
+  g = string_append(g, 2, US" M8S=", big_buffer);
   }
 
 if (*queue_name)
-  s = string_append(s, &size, &sptr, 2, US" Q=", queue_name);
+  g = string_append(g, 2, US" Q=", queue_name);
 
 /* If an addr-spec in a message-id contains a quoted string, it can contain
 any characters except " \ and CR and so in particular it can contain NL!
@@ -3988,7 +4012,7 @@ if (msgid_header)
     &errmsg, &start, &end, &domain, FALSE);
   allow_domain_literals = save_allow_domain_literals;
   if (old_id != NULL)
-    s = string_append(s, &size, &sptr, 2, US" id=", string_printing(old_id));
+    g = string_append(g, 2, US" id=", string_printing(old_id));
   }
 
 /* If subject logging is turned on, create suitable printing-character
@@ -4011,20 +4035,20 @@ if (LOGGING(subject) && subject_header != NULL)
     }
   *p++ = '\"';
   *p = 0;
-  s = string_append(s, &size, &sptr, 2, US" T=", string_printing(big_buffer));
+  g = string_append(g, 2, US" T=", string_printing(big_buffer));
   }
 
 /* Terminate the string: string_cat() and string_append() leave room, but do
 not put the zero in. */
 
-s[sptr] = 0;
+(void) string_from_gstring(g);
 
 /* Create a message log file if message logs are being used and this message is
 not blackholed. Write the reception stuff to it. We used to leave message log
 creation until the first delivery, but this has proved confusing for some
 people. */
 
-if (message_logs && blackholed_by == NULL)
+if (message_logs && !blackholed_by)
   {
   int fd;
 
@@ -4041,11 +4065,8 @@ if (message_logs && blackholed_by == NULL)
     }
 
   if (fd < 0)
-    {
     log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s",
       spool_name, strerror(errno));
-    }
-
   else
     {
     FILE *message_log = fdopen(fd, "a");
@@ -4058,7 +4079,7 @@ if (message_logs && blackholed_by == NULL)
     else
       {
       uschar *now = tod_stamp(tod_log);
-      fprintf(message_log, "%s Received from %s\n", now, s+3);
+      fprintf(message_log, "%s Received from %s\n", now, g->s+3);
       if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
         frozen_by);
       if (queue_only_policy) fprintf(message_log,
@@ -4115,11 +4136,10 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
 
       /* Re-use the log line workspace */
 
-      sptr = 0;
-      s = string_cat(s, &size, &sptr, US"SMTP connection lost after final dot");
-      s = add_host_info_for_log(s, &size, &sptr);
-      s[sptr] = 0;
-      log_write(0, LOG_MAIN, "%s", s);
+      g->ptr = 0;
+      g = string_cat(g, US"SMTP connection lost after final dot");
+      g = add_host_info_for_log(g);
+      log_write(0, LOG_MAIN, "%s", string_from_gstring(g));
 
       /* Delete the files for this aborted message. */
 
@@ -4183,7 +4203,7 @@ if(!smtp_reply)
   log_write(0, LOG_MAIN |
     (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
     (LOGGING(received_sender)? LOG_SENDER : 0),
-    "%s", s);
+    "%s", g->s);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
@@ -4195,7 +4215,7 @@ if(!smtp_reply)
   }
 receive_call_bombout = FALSE;
 
-store_reset(s);   /* The store for the main log message can be reused */
+store_reset(g);   /* The store for the main log message can be reused */
 
 /* If the message is frozen, and freeze_tell is set, do the telling. */
 
@@ -4267,12 +4287,12 @@ if (smtp_input)
 
       else if (chunking_state > CHUNKING_OFFERED)
        {
-        smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+        smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE,
            chunking_datasize, message_size+message_linecount, message_id);
        chunking_state = CHUNKING_OFFERED;
        }
       else
-        smtp_printf("250 OK id=%s\r\n", message_id);
+        smtp_printf("250 OK id=%s\r\n", FALSE, message_id);
 
       if (host_checking)
         fprintf(stdout,
@@ -4286,7 +4306,7 @@ if (smtp_input)
         smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
           fake_response_text);
       else
-        smtp_printf("%.1024s\r\n", smtp_reply);
+        smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
 
     switch (cutthrough_done)
       {