logging
[exim.git] / src / src / receive.c
index 072fee9f1f27aaf4fe50541e1ee17e94d3f5ba69..c783cedfdebf3ad1ce14e1692e3fa621337770ab 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
@@ -14,7 +14,7 @@ extern int dcc_ok;
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
-#include "dmarc.h"
+# include "dmarc.h"
 #endif /* EXPERIMENTAL_DMARC */
 
 /*************************************************
@@ -87,7 +87,7 @@ 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, &untrusted_set_sender, NULL, -1,
+  match_address_list(qnewsender, TRUE, TRUE, CUSS &untrusted_set_sender, NULL, -1,
     0, NULL) == OK;
 }
 
@@ -142,17 +142,16 @@ appearance of "syslog" in it. */
 else
   {
   int sep = ':';              /* Not variable - outside scripts use */
-  uschar *p = log_file_path;
+  const uschar *p = log_file_path;
   name = US"log";
 
   /* An empty log_file_path means "use the default". This is the same as an
   empty item in a list. */
 
   if (*p == 0) p = US":";
-  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))) != NULL)
-    {
-    if (Ustrcmp(path, "syslog") != 0) break;
-    }
+  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
+    if (Ustrcmp(path, "syslog") != 0)
+      break;
 
   if (path == NULL)  /* No log files */
     {
@@ -497,6 +496,8 @@ recipients_list[recipients_count].bmi_optin = bmi_current_optin;
 /* reset optin string pointer for next recipient */
 bmi_current_optin = NULL;
 #endif
+recipients_list[recipients_count].orcpt = NULL;
+recipients_list[recipients_count].dsn_flags = 0;
 recipients_list[recipients_count++].errors_to = NULL;
 }
 
@@ -519,12 +520,12 @@ Arguments:
 Returns:       nothing
 */
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
 static void
 smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
-smtp_message_code(&code, &len, &user_msg, NULL);
+smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
 smtp_respond(code, len, TRUE, user_msg);
 }
 #endif
@@ -680,6 +681,7 @@ while ((ch = (receive_getc)()) != EOF)
 
     case 1:                         /* After written "\n" */
     if (ch == '.') { ch_state = 3; continue; }
+    if (ch == '\r') { ch_state = 2; continue; }
     if (ch != '\n') ch_state = 0; else linelength = -1;
     break;
 
@@ -833,7 +835,15 @@ while ((ch = (receive_getc)()) != EOF)
       ch_state = 4;
       continue;
       }
-    ch_state = 1;                       /* The dot itself is removed */
+    /* The dot was removed at state 3. For a doubled dot, here, reinstate
+    it to cutthrough. The current ch, dot or not, is passed both to cutthrough
+    and to file below. */
+    if (ch == '.')
+      {
+      uschar c= ch;
+      (void) cutthrough_puts(&c, 1);
+      }
+    ch_state = 1;
     break;
 
     case 4:                             /* After [CR] LF . CR */
@@ -951,10 +961,12 @@ if (error_handling == ERRORS_SENDER)
   error_block eblock;
   eblock.next = NULL;
   eblock.text1 = text1;
+  eblock.text2 = US"";
   if (!moan_to_sender(errcode, &eblock, hptr, f, FALSE))
     error_rc = EXIT_FAILURE;
   }
-else fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
+else
+  fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
 (void)fclose(f);
 exim_exit(error_rc);
 }
@@ -984,38 +996,41 @@ Returns:     nothing
 */
 
 static void
-add_acl_headers(uschar *acl_name)
+add_acl_headers(int where, uschar *acl_name)
 {
 header_line *h, *next;
 header_line *last_received = NULL;
 
+switch(where)
+  {
+  case ACL_WHERE_DKIM:
+  case ACL_WHERE_MIME:
+  case ACL_WHERE_DATA:
+    if (cutthrough.fd >= 0 && (acl_removed_headers || acl_added_headers))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
+                       " will not take effect on cutthrough deliveries");
+    return;
+    }
+  }
+
 if (acl_removed_headers != NULL)
   {
   DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name);
 
-  for (h = header_list; h != NULL; h = h->next)
+  for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
     {
-    uschar *list;
-    BOOL include_header;
-
-    if (h->type == htype_old) continue;
-
-    include_header = TRUE;
-    list = acl_removed_headers;
-
+    const uschar * list = acl_removed_headers;
     int sep = ':';         /* This is specified as a colon-separated list */
     uschar *s;
     uschar buffer[128];
-    while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
-            != NULL)
-      {
-      int len = Ustrlen(s);
-      if (header_testname(h, s, len, FALSE))
+
+    while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+      if (header_testname(h, s, Ustrlen(s), FALSE))
        {
        h->type = htype_old;
         DEBUG(D_receive|D_acl) debug_printf("  %s", h->text);
        }
-      }
     }
   acl_removed_headers = NULL;
   DEBUG(D_receive|D_acl) debug_printf(">>\n");
@@ -1113,12 +1128,11 @@ add_host_info_for_log(uschar *s, int *sizeptr, int *ptrptr)
 if (sender_fullhost != NULL)
   {
   s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
-  if ((log_extra_selector & LX_incoming_interface) != 0 &&
-       interface_address != NULL)
+  if (LOGGING(incoming_interface) && interface_address != NULL)
     {
     uschar *ss = string_sprintf(" I=[%s]:%d", interface_address,
       interface_port);
-    s = string_cat(s, sizeptr, ptrptr, ss, Ustrlen(ss));
+    s = string_cat(s, sizeptr, ptrptr, ss);
     }
   }
 if (sender_ident != NULL)
@@ -1224,47 +1238,45 @@ if (Ustrlen(rfc822_file_path) > 0)
 if (rc == OK)
   {
   uschar temp_path[1024];
-  int n;
-  struct dirent *entry;
-  DIR *tempdir;
+  struct dirent * entry;
+  DIR * tempdir;
 
-  (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
-    message_id);
+  (void) string_format(temp_path, sizeof(temp_path), "%s/scan/%s",
+    spool_directory, message_id);
 
   tempdir = opendir(CS temp_path);
-  n = 0;
-  do
+  for (;;)
     {
-    entry = readdir(tempdir);
-    if (entry == NULL) break;
-    if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0)
+    if (!(entry = readdir(tempdir)))
+      break;
+    if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
-      (void)string_format(rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
-      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
+      (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
+       "%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
+      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+       rfc822_file_path);
       break;
       }
-    } while (1);
+    }
   closedir(tempdir);
 
-  if (entry != NULL)
+  if (entry)
     {
-    mbox_file = Ufopen(rfc822_file_path,"rb");
-    if (mbox_file == NULL)
+    if ((mbox_file = Ufopen(rfc822_file_path, "rb")))
       {
-      log_write(0, LOG_PANIC,
-         "acl_smtp_mime: can't open RFC822 spool file, skipping.");
-      unlink(CS rfc822_file_path);
-      goto END_MIME_ACL;
+      /* set RFC822 expansion variable */
+      mime_is_rfc822 = 1;
+      mime_part_count_buffer = mime_part_count;
+      goto MIME_ACL_CHECK;
       }
-    /* set RFC822 expansion variable */
-    mime_is_rfc822 = 1;
-    mime_part_count_buffer = mime_part_count;
-    goto MIME_ACL_CHECK;
+    log_write(0, LOG_PANIC,
+       "acl_smtp_mime: can't open RFC822 spool file, skipping.");
+    unlink(CS rfc822_file_path);
     }
   }
 
 END_MIME_ACL:
-add_acl_headers(US"MIME");
+add_acl_headers(ACL_WHERE_MIME, US"MIME");
 if (rc == DISCARD)
   {
   recipients_count = 0;
@@ -1277,10 +1289,12 @@ else if (rc != OK)
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
-  if (smtp_input && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) {
-    *smtp_yield_ptr = FALSE;    /* No more messsages after dropped connection */
+  if (  smtp_input
+     && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+    {
+    *smtp_yield_ptr = FALSE;    /* No more messages after dropped connection */
     *smtp_reply_ptr = US"";     /* Indicate reply already sent */
-  }
+    }
   message_id[0] = 0;            /* Indicate no message accepted */
   return FALSE;                 /* Cause skip to end of receive function */
   }
@@ -1304,7 +1318,7 @@ if (recipients_count == 1) received_for = recipients_list[0].address;
 received = expand_string(received_header_text);
 received_for = NULL;
 
-if (received == NULL)
+if (!received)
   {
   if(spool_name[0] != 0)
     Uunlink(spool_name);           /* Lose the data file */
@@ -1454,7 +1468,7 @@ BOOL resents_exist = FALSE;
 uschar *resent_prefix = US"";
 uschar *blackholed_by = NULL;
 uschar *blackhole_log_msg = US"";
-enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done;
+enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED;
 
 flock_t lock_data;
 error_block *bad_addresses = NULL;
@@ -2300,9 +2314,23 @@ if (extract_recip)
         pp = recipient = store_get(ss - s + 1);
         for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
+
+#ifdef SUPPORT_I18N
+       {
+       BOOL b = allow_utf8_domains;
+       allow_utf8_domains = TRUE;
+#endif
         recipient = parse_extract_address(recipient, &errmess, &start, &end,
           &domain, FALSE);
 
+#ifdef SUPPORT_I18N
+       if (string_is_utf8(recipient))
+         message_smtputf8 = TRUE;
+       else
+         allow_utf8_domains = b;
+       }
+#endif
+
         /* Keep a list of all the bad addresses so we can send a single
         error message at the end. However, an empty address is not an error;
         just ignore it. This can come from an empty group list like
@@ -2443,7 +2471,7 @@ it will fit. */
 to be the least significant base-62 digit of the time of arrival. Otherwise
 ensure that it is an empty string. */
 
-message_subdir[0] = split_spool_directory? message_id[5] : 0;
+message_subdir[0] = split_spool_directory ? message_id[5] : 0;
 
 /* Now that we have the message-id, if there is no message-id: header, generate
 one, but only for local (without suppress_local_fixups) or submission mode
@@ -2512,7 +2540,7 @@ if (msgid_header == NULL &&
 rewriting. Must copy the count, because later ACLs and the local_scan()
 function may mess with the real recipients. */
 
-if ((log_extra_selector & LX_received_recipients) != 0)
+if (LOGGING(received_recipients))
   {
   raw_recipients = store_get(recipients_count * sizeof(uschar *));
   for (i = 0; i < recipients_count; i++)
@@ -2803,12 +2831,11 @@ if (filter_test != FTEST_NONE)
   }
 
 /* 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)
+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)
   {
   if (received_count > received_headers_max)
     {
@@ -2826,7 +2853,7 @@ if (cutthrough_fd >= 0)
     goto TIDYUP;                             /* Skip to end of function */
     }
   received_header_gen();
-  add_acl_headers(US"MAIL or RCPT");
+  add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
   (void) cutthrough_headers_send();
   }
 
@@ -2836,16 +2863,17 @@ to access it both via a file descriptor and a stream. Try to make the
 directory if it isn't there. Note re use of sprintf: spool_directory
 is checked on input to be < 200 characters long. */
 
-sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory, message_subdir,
-  message_id);
-data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
-if (data_fd < 0)
+snprintf(CS spool_name, sizeof(spool_name), "%s/input/%s/%s/%s-D",
+  spool_directory, queue_name, message_subdir, message_id);
+DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
+
+if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
   {
   if (errno == ENOENT)
     {
-    uschar temp[16];
-    sprintf(CS temp, "input/%s", message_subdir);
-    if (message_subdir[0] == 0) temp[5] = 0;
+    uschar * temp = string_sprintf("input%s%s%s%s",
+           *queue_name ? "/" : "", queue_name,
+           *message_subdir ? "/" : "", message_subdir);
     (void)directory_make(spool_directory, temp, INPUT_DIRECTORY_MODE, TRUE);
     data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
     }
@@ -3118,7 +3146,7 @@ if (received_header->text == NULL)        /* Non-cutthrough case */
   /* If an ACL from any RCPT commands set up any warning headers to add, do so
   now, before running the DATA ACL. */
 
-  add_acl_headers(US"MAIL or RCPT");
+  add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
   }
 else
   message_body_size = (fstat(data_fd, &statbuf) == 0)?
@@ -3170,7 +3198,7 @@ else
         else
           {
           int sep = 0;
-          uschar *ptr = dkim_verify_signers_expanded;
+          const uschar *ptr = dkim_verify_signers_expanded;
           uschar *item = NULL;
           uschar *seen_items = NULL;
           int     seen_items_size = 0;
@@ -3180,58 +3208,63 @@ else
           rc = OK;
           while ((item = string_nextinlist(&ptr, &sep,
                                            itembuf,
-                                           sizeof(itembuf))) != NULL)
+                                           sizeof(itembuf))))
             {
             /* Prevent running ACL for an empty item */
             if (!item || (item[0] == '\0')) continue;
-            /* Only run ACL once for each domain or identity, no matter how often it
-               appears in the expanded list. */
-            if (seen_items != NULL)
+
+            /* 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;
               uschar seen_item_buf[256];
-              uschar *seen_items_list = seen_items;
-              int seen_this_item = 0;
+              const uschar *seen_items_list = seen_items;
+              BOOL seen_this_item = FALSE;
 
               while ((seen_item = string_nextinlist(&seen_items_list, &sep,
                                                     seen_item_buf,
-                                                    sizeof(seen_item_buf))) != NULL)
-                {
-                  if (Ustrcmp(seen_item,item) == 0)
-                    {
-                      seen_this_item = 1;
-                      break;
-                    }
-                }
-
-              if (seen_this_item > 0)
+                                                    sizeof(seen_item_buf))))
+               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);
+                  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, ":");
               }
 
-            seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,item);
+            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);
+              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);
+            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
+                 &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("dkim acl not ok");
-                break;
-              }
+             {
+             DEBUG(D_receive)
+               debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+                 "skipping remaining items\n", rc, item);
+             cancel_cutthrough_connection("dkim acl not ok");
+             break;
+             }
             }
-          add_acl_headers(US"DKIM");
+          add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
           if (rc == DISCARD)
             {
             recipients_count = 0;
@@ -3243,7 +3276,7 @@ else
             {
             Uunlink(spool_name);
             if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
-              smtp_yield = FALSE;    /* No more messsages after dropped connection */
+              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 */
@@ -3264,8 +3297,8 @@ else
     dmarc_up = dmarc_store_data(from_header);
 #endif /* EXPERIMENTAL_DMARC */
 
-#ifdef EXPERIMENTAL_PRDR
-    if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr != NULL )
+#ifndef DISABLE_PRDR
+    if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr)
       {
       unsigned int c;
       int all_pass = OK;
@@ -3333,7 +3366,7 @@ else
       }
     else
       prdr_requested = FALSE;
-#endif /* EXPERIMENTAL_PRDR */
+#endif /* !DISABLE_PRDR */
 
     /* Check the recipients count again, as the MIME ACL might have changed
     them. */
@@ -3341,7 +3374,7 @@ else
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
-      add_acl_headers(US"DATA");
+      add_acl_headers(ACL_WHERE_DATA, US"DATA");
       if (rc == DISCARD)
         {
         recipients_count = 0;
@@ -3361,7 +3394,7 @@ else
        dcc_ok = 0;
 #endif
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
-          smtp_yield = FALSE;    /* No more messsages after dropped connection */
+          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 */
@@ -3424,7 +3457,7 @@ else
           /* Does not return */
           }
         }
-      add_acl_headers(US"non-SMTP");
+      add_acl_headers(ACL_WHERE_NOTSMTP, US"non-SMTP");
       }
     }
 
@@ -3552,7 +3585,7 @@ else
     goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
@@ -3561,7 +3594,7 @@ else
     break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
@@ -3617,11 +3650,11 @@ signal(SIGINT, SIG_IGN);
 deliver_firsttime = TRUE;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_run == 1) {
-  /* rewind data file */
+if (bmi_run == 1)
+  /* rewind data file */
   lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
   bmi_verdicts = bmi_process_message(header_list, data_fd);
-};
+  }
 #endif
 
 /* Update the timstamp in our Received: header to account for any time taken by
@@ -3659,7 +3692,6 @@ if (host_checking || blackholed_by != NULL)
 /* Write the -H file */
 
 else
-  {
   if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
     {
     log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
@@ -3679,7 +3711,6 @@ else
       /* Does not return */
       }
     }
-  }
 
 
 /* The message has now been successfully received. */
@@ -3726,36 +3757,40 @@ if (message_reference != NULL)
 s = add_host_info_for_log(s, &size, &sptr);
 
 #ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+if (LOGGING(tls_cipher) && tls_in.cipher)
   s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
-if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-     tls_in.cipher != NULL)
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   s = string_append(s, &size, &sptr, 2, US" CV=",
     tls_in.certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
   s = string_append(s, &size, &sptr, 3, US" DN=\"",
     string_printing(tls_in.peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
+if (LOGGING(tls_sni) && tls_in.sni)
   s = string_append(s, &size, &sptr, 3, US" SNI=\"",
     string_printing(tls_in.sni), US"\"");
 #endif
 
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
   {
   s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
   if (authenticated_id != NULL)
     {
     s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
-    if (log_extra_selector & LX_smtp_mailauth  &&  authenticated_sender != NULL)
+    if (LOGGING(smtp_mailauth) && authenticated_sender != NULL)
       s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
     }
   }
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
 if (prdr_requested)
   s = string_append(s, &size, &sptr, 1, US" PRDR");
 #endif
 
+#ifdef SUPPORT_PROXY
+if (proxy_session && LOGGING(proxy))
+  s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
+#endif
+
 sprintf(CS big_buffer, "%d", msg_size);
 s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
 
@@ -3763,12 +3798,15 @@ s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
    0 ... no BODY= used
    7 ... 7BIT
    8 ... 8BITMIME */
-if (log_extra_selector & LX_8bitmime)
+if (LOGGING(8bitmime))
   {
   sprintf(CS big_buffer, "%d", body_8bitmime);
   s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
   }
 
+if (*queue_name)
+  s = string_append(s, &size, &sptr, 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!
 Therefore, make sure we use a printing-characters only version for the log.
@@ -3789,7 +3827,7 @@ if (msgid_header != NULL)
 /* If subject logging is turned on, create suitable printing-character
 text. By expanding $h_subject: we make use of the MIME decoding. */
 
-if ((log_extra_selector & LX_subject) != 0 && subject_header != NULL)
+if (LOGGING(subject) && subject_header != NULL)
   {
   int i;
   uschar *p = big_buffer;
@@ -3823,15 +3861,16 @@ if (message_logs && blackholed_by == NULL)
   {
   int fd;
 
-  sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory, message_subdir,
-    message_id);
-  fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
-
-  if (fd < 0 && errno == ENOENT)
+  snprintf(CS spool_name, sizeof(spool_name), "%s/msglog/%s/%s/%s",
+    spool_directory, queue_name, message_subdir, message_id);
+  
+  if (  (fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+     && errno == ENOENT
+     )
     {
-    uschar temp[16];
-    sprintf(CS temp, "msglog/%s", message_subdir);
-    if (message_subdir[0] == 0) temp[6] = 0;
+    uschar * temp = string_sprintf("msglog%s%s%s%s",
+                       *queue_name ? "/" : "", queue_name,
+                       *message_subdir ? "/" : "", message_subdir);
     (void)directory_make(spool_directory, temp, MSGLOG_DIRECTORY_MODE, TRUE);
     fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
     }
@@ -3910,23 +3949,23 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
       /* Re-use the log line workspace */
 
       sptr = 0;
-      s = string_cat(s, &size, &sptr, msg, Ustrlen(msg));
+      s = string_cat(s, &size, &sptr, msg);
       s = add_host_info_for_log(s, &size, &sptr);
       s[sptr] = 0;
       log_write(0, LOG_MAIN, "%s", s);
 
       /* Delete the files for this aborted message. */
 
-      sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-        message_subdir, message_id);
+      snprintf(CS spool_name, sizeof(spool_name), "%s/input/%s/%s/%s-D",
+       spool_directory, queue_name, message_subdir, message_id);
       Uunlink(spool_name);
 
-      sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-        message_subdir, message_id);
+      snprintf(CS spool_name, sizeof(spool_name), "%s/input/%s/%s/%s-H",
+       spool_directory, queue_name, message_subdir, message_id);
       Uunlink(spool_name);
 
-      sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-        message_subdir, message_id);
+      snprintf(CS spool_name, sizeof(spool_name), "%s/msglog/%s/%s/%s",
+       spool_directory, queue_name, message_subdir, message_id);
       Uunlink(spool_name);
 
       goto TIDYUP;
@@ -3949,10 +3988,9 @@ for this message. */
 
    XXX We do not handle queue-only, freezing, or blackholes.
 */
-cutthrough_done = NOT_TRIED;
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
   {
-  uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */
+  uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the message */
                                        /* Logging was done in finaldot() */
   switch(msg[0])
     {
@@ -3972,15 +4010,15 @@ if(cutthrough_fd >= 0)
     }
   }
 
-if(smtp_reply == NULL
-#ifdef EXPERIMENTAL_PRDR
-                     || prdr_requested
+#ifndef DISABLE_PRDR
+if(!smtp_reply || prdr_requested)
+#else
+if(!smtp_reply)
 #endif
-  )
   {
   log_write(0, LOG_MAIN |
-    (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) |
-    (((log_extra_selector & LX_received_sender) != 0)? LOG_SENDER : 0),
+    (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
+    (LOGGING(received_sender)? LOG_SENDER : 0),
     "%s", s);
 
   /* Log any control actions taken by an ACL or local_scan(). */
@@ -4055,7 +4093,7 @@ if (smtp_input)
         {
         uschar *code = US"250";
         int len = 3;
-        smtp_message_code(&code, &len, &user_msg, NULL);
+        smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
         smtp_respond(code, len, TRUE, user_msg);
         }
 
@@ -4083,20 +4121,20 @@ if (smtp_input)
       {
       case ACCEPTED: log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
       case PERM_REJ: {                                 /* Delete spool files */
-             sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-               message_subdir, message_id);
+             snprintf(CS spool_name, sizeof(spool_name), "%s/input/%s/%s/%s-D",
+               spool_directory, queue_name, message_subdir, message_id);
              Uunlink(spool_name);
-             sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-               message_subdir, message_id);
+             snprintf(CS spool_name, sizeof(spool_name), "%s/input/%s/%s/%s-H",
+               spool_directory, queue_name, message_subdir, message_id);
              Uunlink(spool_name);
-             sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-               message_subdir, message_id);
+             snprintf(CS spool_name, sizeof(spool_name), "%s/msglog/%s/%s/%s",
+               spool_directory, queue_name, message_subdir, message_id);
              Uunlink(spool_name);
              }
       case TMP_REJ: message_id[0] = 0;   /* Prevent a delivery from starting */
       default:break;
       }
-    cutthrough_delivery = FALSE;
+    cutthrough.delivery = FALSE;
     }
 
   /* For batched SMTP, generate an error message on failure, and do
@@ -4114,9 +4152,9 @@ starting. */
 
 if (blackholed_by != NULL)
   {
-  uschar *detail = (local_scan_data != NULL)?
-    string_printing(local_scan_data) :
-    string_sprintf("(%s discarded recipients)", blackholed_by);
+  const uschar *detail = local_scan_data
+    ? string_printing(local_scan_data)
+    string_sprintf("(%s discarded recipients)", blackholed_by);
   log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
   log_write(0, LOG_MAIN, "Completed");
   message_id[0] = 0;