Merge branch 'CHUNKING'
[exim.git] / src / src / transports / smtp.c
index 58a59433d9f005c2a7cdb489af7646d993d8179b..52b2b913f4fde5eb99e835ceb27c17189145b727 100644 (file)
@@ -12,6 +12,8 @@
 #define PENDING_DEFER   (PENDING + DEFER)
 #define PENDING_OK      (PENDING + OK)
 
+#define DELIVER_BUFFER_SIZE 4096
+
 
 /* Options specific to the smtp transport. This transport also supports LMTP
 over TCP/IP. The options must be in alphabetic order (note that "_" comes
@@ -282,6 +284,7 @@ static uschar *rf_names[] = { US"NEVER", US"SUCCESS", US"FAILURE", US"DELAY" };
 static uschar *smtp_command;   /* Points to last cmd for error messages */
 static uschar *mail_command;   /* Points to MAIL cmd for error messages */
 static BOOL    update_waiting; /* TRUE to update the "wait" database */
+static BOOL    pipelining_active; /* current transaction is in pipe mode */
 
 
 /*************************************************
@@ -510,13 +513,7 @@ static BOOL
 check_response(host_item *host, int *errno_value, int more_errno,
   uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
 {
-uschar *pl = US"";
-
-if (smtp_use_pipelining &&
-    (Ustrcmp(smtp_command, "MAIL") == 0 ||
-     Ustrcmp(smtp_command, "RCPT") == 0 ||
-     Ustrcmp(smtp_command, "DATA") == 0))
-  pl = US"pipelined ";
+uschar * pl = pipelining_active ? US"pipelined " : US"";
 
 *yield = '4';    /* Default setting is to give a temporary error */
 
@@ -786,6 +783,7 @@ if (pending_MAIL)
   count--;
   if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout))
     {
+    DEBUG(D_transport) debug_printf("bad response for MAIL\n");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
     if (errno == 0 && buffer[0] != 0)
       {
@@ -1225,7 +1223,8 @@ switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
   case DNS_AGAIN:
     return DEFER; /* just defer this TLS'd conn */
 
-  case DNS_NOMATCH:
+  case DNS_NODATA:     /* no TLSA RR for this lookup */
+  case DNS_NOMATCH:    /* no records at all for this lookup */
     return dane_required ? FAIL : FAIL_FORCED;
 
   default:
@@ -1363,57 +1362,93 @@ return checks;
 
 
 /* Callback for emitting a BDAT data chunk header.
-Flush any buffered SMTP commands first.
-Reap SMTP command responses if not the BDAT LAST.
 
-A nonlast request that is size zero is special-cased to only flush the
-command buffer and reap all outstanding responses.
+If given a nonzero size, first flush any buffered SMTP commands
+then emit the command.
+
+Reap previous SMTP command responses if requested.
+Reap one SMTP command response if requested.
 
 Returns:       OK or ERROR
 */
 
 static int
 smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
-  unsigned chunk_size, BOOL chunk_last)
+  unsigned chunk_size, unsigned flags)
 {
 smtp_transport_options_block * ob =
   (smtp_transport_options_block *)(tctx->tblock->options_block);
-uschar buffer[128];
+int cmd_count = 0;
+int prev_cmd_count;
+uschar * buffer = tctx->buffer;
 
-if (  (tctx->cmd_count = chunk_size == 0 && !chunk_last
 
-       /* Handle flush request */
-       ?  smtp_write_command(tctx->outblock, FALSE, NULL)
+/* Write SMTP chunk header command */
 
-       /* Write SMTP chunk header command */
-       : smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
-                             chunk_size, chunk_last ? " LAST" : "")
-      )
-    < 0)
-  return ERROR;
+if (chunk_size > 0)
+  if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
+                             chunk_size,
+                             flags & tc_chunk_last ? " LAST" : "")
+     ) < 0) return ERROR;
+
+prev_cmd_count = cmd_count += tctx->cmd_count;
 
-if (chunk_last)
-  return OK;
+/* Reap responses for any previous, but not one we just emitted */
 
-/* Reap responses for this and any previous, and error out on failure */
-debug_printf("(look for %d responses)\n", tctx->cmd_count);
+if (chunk_size > 0)
+  prev_cmd_count--;
+if (tctx->pending_BDAT)
+  prev_cmd_count--;
 
-switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes,
-       tctx->sync_addr, tctx->host, tctx->cmd_count,
-       ob->address_retry_include_sender,
-       tctx->pending_MAIL, 0,
-       tctx->inblock,
-       ob->command_timeout,
-       buffer, sizeof(buffer)))
+if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
   {
-  case 1:                              /* 2xx (only) => OK */
-  case 3:                              /* 2xx & 5xx => OK & progress made */
-  case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */
-  case 0: return OK;                   /* No 2xx or 5xx, but no probs */
 
-  case -1:                             /* Timeout on RCPT */
-  default: return ERROR;               /* I/O error, or any MAIL/DATA error */
+  switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes,
+         tctx->sync_addr, tctx->host, prev_cmd_count,
+         ob->address_retry_include_sender,
+         tctx->pending_MAIL, 0,
+         tctx->inblock,
+         ob->command_timeout,
+         buffer, DELIVER_BUFFER_SIZE))
+    {
+    case 1:                            /* 2xx (only) => OK */
+    case 3: tctx->good_RCPT = TRUE;    /* 2xx & 5xx => OK & progress made */
+    case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */
+    case 0: break;                     /* No 2xx or 5xx, but no probs */
+
+    case -1:                           /* Timeout on RCPT */
+    default: return ERROR;             /* I/O error, or any MAIL/DATA error */
+    }
+  cmd_count = 1;
+  if (!tctx->pending_BDAT)
+    pipelining_active = FALSE;
   }
+
+/* Reap response for the cmd we just emitted, or an outstanding BDAT */
+
+if (flags & tc_reap_one  ||  tctx->pending_BDAT)
+  {
+  if (!smtp_read_response(tctx->inblock, buffer, DELIVER_BUFFER_SIZE, '2',
+       ob->command_timeout))
+    {
+    if (errno == 0 && buffer[0] == '4')
+      {
+      errno = ERRNO_DATA4XX;   /*XXX does this actually get used? */
+      tctx->first_addr->more_errno |=
+       ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      }
+    return ERROR;
+    }
+  cmd_count--;
+  tctx->pending_BDAT = FALSE;
+  pipelining_active = FALSE;
+  }
+else if (chunk_size > 0)
+  tctx->pending_BDAT = TRUE;
+
+
+tctx->cmd_count = cmd_count;
+return OK;
 }
 
 
@@ -1487,7 +1522,7 @@ BOOL completed_address = FALSE;
 BOOL esmtp = TRUE;
 BOOL pending_MAIL;
 BOOL pass_message = FALSE;
-uschar peer_offered = 0;       /*XXX should this be handed on cf. tls_offered, smtp_use_dsn ? */
+uschar peer_offered = 0;
 #ifndef DISABLE_PRDR
 BOOL prdr_active;
 #endif
@@ -1514,7 +1549,7 @@ uschar *helo_data = NULL;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 uschar *p;
-uschar buffer[4096];
+uschar buffer[DELIVER_BUFFER_SIZE];
 uschar inbuffer[4096];
 uschar outbuffer[4096];
 
@@ -1722,7 +1757,7 @@ goto SEND_QUIT;
 #ifdef SUPPORT_TLS
   if (smtps)
     {
-    tls_offered = TRUE;
+    smtp_peer_options |= PEER_OFFERED_TLS;
     suppress_tls = FALSE;
     ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
@@ -1771,7 +1806,10 @@ goto SEND_QUIT;
     if (!good_response) goto RESPONSE_FAILED;
     }
 
+  peer_offered = smtp_peer_options = 0;
+
   if (esmtp || lmtp)
+    {
     peer_offered = ehlo_response(buffer, Ustrlen(buffer),
       PEER_OFFERED_TLS /* others checked later */
       );
@@ -1779,14 +1817,15 @@ goto SEND_QUIT;
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
 
 #ifdef SUPPORT_TLS
-  tls_offered = !!(peer_offered & PEER_OFFERED_TLS);
+    smtp_peer_options |= peer_offered & PEER_OFFERED_TLS;
 #endif
+    }
   }
 
 /* For continuing deliveries down the same channel, the socket is the standard
 input, and we don't need to redo EHLO here (but may need to do so for TLS - see
 below). Set up the pointer to where subsequent commands will be left, for
-error messages. Note that smtp_use_size and smtp_use_pipelining will have been
+error messages. Note that smtp_peer_options will have been
 set from the command line if they were set in the process that passed the
 connection on. */
 
@@ -1811,7 +1850,7 @@ the client not be required to use TLS. If the response is bad, copy the buffer
 for error analysis. */
 
 #ifdef SUPPORT_TLS
-if (  tls_offered
+if (  smtp_peer_options & PEER_OFFERED_TLS
    && !suppress_tls
    && verify_check_given_host(&ob->hosts_avoid_tls, host) != OK)
   {
@@ -1873,6 +1912,7 @@ if (  tls_offered
 
     /* TLS session is set up */
 
+    smtp_peer_options_wrap = smtp_peer_options;
     for (addr = addrlist; addr; addr = addr->next)
       if (addr->transport_return == PENDING_DEFER)
         {
@@ -1942,6 +1982,7 @@ if (tls_out.active >= 0)
   helo_response = string_copy(buffer);
 #endif
   if (!good_response) goto RESPONSE_FAILED;
+  smtp_peer_options = 0;
   }
 
 /* If the host is required to use a secure channel, ensure that we
@@ -1956,8 +1997,8 @@ else if (  smtps
   {
   save_errno = ERRNO_TLSREQUIRED;
   message = string_sprintf("a TLS session is required, but %s",
-    tls_offered ? "an attempt to start TLS failed"
-               : "the server did not offer TLS support");
+    smtp_peer_options & PEER_OFFERED_TLS
+    ? "an attempt to start TLS failed" : "the server did not offer TLS support");
   goto TLS_FAILED;
   }
 #endif /*SUPPORT_TLS*/
@@ -1974,6 +2015,7 @@ if (continue_hostname == NULL
     )
   {
   if (esmtp || lmtp)
+    {
     peer_offered = ehlo_response(buffer, Ustrlen(buffer),
       0 /* no TLS */
       | (lmtp && ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
@@ -1981,7 +2023,7 @@ if (continue_hostname == NULL
       | PEER_OFFERED_PRDR
 #ifdef SUPPORT_I18N
       | (addrlist->prop.utf8_msg ? PEER_OFFERED_UTF8 : 0)
-       /*XXX if we hand peercaps on to continued-conn processes,
+       /*XXX if we hand peercaps on to continued-conn processes,
              must not depend on this addr */
 #endif
       | PEER_OFFERED_DSN
@@ -1989,60 +2031,64 @@ if (continue_hostname == NULL
       | (ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0)
       );
 
-  /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
-  lmtp_ignore_quota option was set. */
+    /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+    lmtp_ignore_quota option was set. */
 
-  igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
+    igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
 
-  /* If the response to EHLO specified support for the SIZE parameter, note
-  this, provided size_addition is non-negative. */
+    /* If the response to EHLO specified support for the SIZE parameter, note
+    this, provided size_addition is non-negative. */
 
-  smtp_use_size = !!(peer_offered & PEER_OFFERED_SIZE);
+    smtp_peer_options |= peer_offered & PEER_OFFERED_SIZE;
 
-  /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
-  the current host, esmtp will be false, so PIPELINING can never be used. If
-  the current host matches hosts_avoid_pipelining, don't do it. */
+    /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
+    the current host, esmtp will be false, so PIPELINING can never be used. If
+    the current host matches hosts_avoid_pipelining, don't do it. */
 
-  smtp_use_pipelining = peer_offered & PEER_OFFERED_PIPE
-    && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK;
+    if (  peer_offered & PEER_OFFERED_PIPE
+       && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK)
+      smtp_peer_options |= PEER_OFFERED_PIPE;
 
-  DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
-    smtp_use_pipelining ? "" : "not ");
+    DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
+      smtp_peer_options & PEER_OFFERED_PIPE ? "" : "not ");
 
-  if (  peer_offered & PEER_OFFERED_CHUNKING
-     && verify_check_given_host(&ob->hosts_try_chunking, host) != OK)
-    peer_offered &= ~PEER_OFFERED_CHUNKING;
+    if (  peer_offered & PEER_OFFERED_CHUNKING
+       && verify_check_given_host(&ob->hosts_try_chunking, host) != OK)
+      peer_offered &= ~PEER_OFFERED_CHUNKING;
 
-  if (peer_offered & PEER_OFFERED_CHUNKING)
-    {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
+    if (peer_offered & PEER_OFFERED_CHUNKING)
+      {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
 
 #ifndef DISABLE_PRDR
-  if (  peer_offered & PEER_OFFERED_PRDR
-     && verify_check_given_host(&ob->hosts_try_prdr, host) != OK)
-    peer_offered &= ~PEER_OFFERED_PRDR;
+    if (  peer_offered & PEER_OFFERED_PRDR
+       && verify_check_given_host(&ob->hosts_try_prdr, host) != OK)
+      peer_offered &= ~PEER_OFFERED_PRDR;
 
-  if (peer_offered & PEER_OFFERED_PRDR)
-    {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+    if (peer_offered & PEER_OFFERED_PRDR)
+      {DEBUG(D_transport) debug_printf("PRDR usable\n");}
 #endif
 
-  /* Note if the server supports DSN */
-  smtp_use_dsn = !!(peer_offered & PEER_OFFERED_DSN);
-  DEBUG(D_transport) debug_printf("%susing DSN\n", smtp_use_dsn ? "" : "not ");
+    /* Note if the server supports DSN */
+    smtp_peer_options |= peer_offered & PEER_OFFERED_DSN;
+    DEBUG(D_transport) debug_printf("%susing DSN\n",
+                       peer_offered & PEER_OFFERED_DSN ? "" : "not ");
 
-  /* Note if the response to EHLO specifies support for the AUTH extension.
-  If it has, check that this host is one we want to authenticate to, and do
-  the business. The host name and address must be available when the
-  authenticator's client driver is running. */
+    /* Note if the response to EHLO specifies support for the AUTH extension.
+    If it has, check that this host is one we want to authenticate to, and do
+    the business. The host name and address must be available when the
+    authenticator's client driver is running. */
 
-  switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
-                           ob, esmtp, &inblock, &outblock))
-    {
-    default:           goto SEND_QUIT;
-    case OK:           break;
-    case FAIL_SEND:    goto SEND_FAILED;
-    case FAIL:         goto RESPONSE_FAILED;
+    switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
+                             ob, esmtp, &inblock, &outblock))
+      {
+      default:         goto SEND_QUIT;
+      case OK:         break;
+      case FAIL_SEND:  goto SEND_FAILED;
+      case FAIL:       goto RESPONSE_FAILED;
+      }
     }
   }
+pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE);
 
 /* The setting up of the SMTP call is now complete. Any subsequent errors are
 message-specific. */
@@ -2088,6 +2134,16 @@ if (tblock->filter_command != NULL)
     yield = ERROR;
     goto SEND_QUIT;
     }
+
+  if (  transport_filter_argv
+     && *transport_filter_argv
+     && **transport_filter_argv
+     && peer_offered & PEER_OFFERED_CHUNKING
+     )
+    {
+    peer_offered &= ~PEER_OFFERED_CHUNKING;
+    DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
+    }
   }
 
 
@@ -2117,7 +2173,7 @@ included in the count.) */
 p = buffer;
 *p = 0;
 
-if (smtp_use_size)
+if (peer_offered & PEER_OFFERED_SIZE)
   {
   sprintf(CS p, " SIZE=%d", message_size+message_linecount+ob->size_addition);
   while (*p) p++;
@@ -2161,7 +2217,7 @@ for (dsn_all_lasthop = TRUE, addr = first_addr;
 
 /* Add any DSN flags to the mail command */
 
-if (smtp_use_dsn && !dsn_all_lasthop)
+if (peer_offered & PEER_OFFERED_DSN && !dsn_all_lasthop)
   {
   if (dsn_ret == dsn_ret_hdrs)
     { Ustrcpy(p, " RET=HDRS"); p += 9; }
@@ -2219,7 +2275,7 @@ pending_MAIL = TRUE;     /* The block starts with MAIL */
     }
 #endif
 
-  rc = smtp_write_command(&outblock, smtp_use_pipelining,
+  rc = smtp_write_command(&outblock, pipelining_active,
          "MAIL FROM:<%s>%s\r\n", s, buffer);
   }
 
@@ -2266,21 +2322,22 @@ for (addr = first_addr;
   BOOL no_flush;
   uschar * rcpt_addr;
 
-  addr->dsn_aware = smtp_use_dsn ? dsn_support_yes : dsn_support_no;
+  addr->dsn_aware = peer_offered & PEER_OFFERED_DSN
+    ? dsn_support_yes : dsn_support_no;
 
   if (addr->transport_return != PENDING_DEFER) continue;
 
   address_count++;
-  no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next);
+  no_flush = pipelining_active && (!mua_wrapper || addr->next);
 
   /* Add any DSN flags to the rcpt command and add to the sent string */
 
   p = buffer;
   *p = 0;
 
-  if (smtp_use_dsn && !(addr->dsn_flags & rf_dsnlasthop))
+  if (peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop))
     {
-    if ((addr->dsn_flags & rf_dsnflags) != 0)
+    if (addr->dsn_flags & rf_dsnflags)
       {
       int i;
       BOOL first = TRUE;
@@ -2379,7 +2436,7 @@ If using CHUNKING, do not send a BDAT until we know how big a chunk we want
 to send is. */
 
 if (  !(peer_offered & PEER_OFFERED_CHUNKING)
-   && (ok || (smtp_use_pipelining && !mua_wrapper)))
+   && (ok || (pipelining_active && !mua_wrapper)))
   {
   int count = smtp_write_command(&outblock, FALSE, "DATA\r\n");
 
@@ -2399,6 +2456,7 @@ if (  !(peer_offered & PEER_OFFERED_CHUNKING)
     case -1: goto END_OFF;               /* Timeout on RCPT */
     default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
     }
+  pipelining_active = FALSE;
   }
 
 /* If there were no good recipients (but otherwise there have been no
@@ -2426,7 +2484,7 @@ else
     | (tblock->headers_only    ? topt_no_body : 0)
     | (tblock->return_path_add ? topt_add_return_path : 0)
     | (tblock->delivery_date_add ? topt_add_delivery_date : 0)
-    | (tblock->envelope_to_add ? topt_add_envelope_to : 0),
+    | (tblock->envelope_to_add ? topt_add_envelope_to : 0)
   };
 
   /* If using CHUNKING we need a callback from the generic transport
@@ -2445,7 +2503,11 @@ else
     tctx.first_addr = first_addr;
     tctx.sync_addr = &sync_addr;
     tctx.pending_MAIL = pending_MAIL;
+    tctx.pending_BDAT = FALSE;
+    tctx.good_RCPT = ok;
     tctx.completed_address = &completed_address;
+    tctx.cmd_count = 0;
+    tctx.buffer = buffer;
     }
   else
     tctx.options |= topt_end_dot;
@@ -2453,6 +2515,11 @@ else
   /* Save the first address of the next batch. */
   first_addr = addr;
 
+  /* Responses from CHUNKING commands go in buffer.  Otherwise,
+  there has not been a response. */
+
+  buffer[0] = 0;
+
   sigalrm_seen = FALSE;
   transport_write_timeout = ob->data_timeout;
   smtp_command = US"sending data block";   /* For error messages */
@@ -2477,13 +2544,11 @@ else
   transport_write_timeout = 0;   /* for subsequent transports */
 
   /* Failure can either be some kind of I/O disaster (including timeout),
-  or the failure of a transport filter or the expansion of added headers. */
+  or the failure of a transport filter or the expansion of added headers.
+  Or, when CHUNKING, it can be a protocol-detected failure. */
 
   if (!ok)
-    {
-    buffer[0] = 0;              /* There hasn't been a response */
     goto RESPONSE_FAILED;
-    }
 
   /* We used to send the terminating "." explicitly here, but because of
   buffering effects at both ends of TCP/IP connections, you don't gain
@@ -2523,16 +2588,16 @@ else
     {
     ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3',
       ob->final_timeout);
-    if (!ok && errno == 0)
-      switch(buffer[0])
-        {
-       case '2': prdr_active = FALSE;
-                 ok = TRUE;
-                 break;
-       case '4': errno = ERRNO_DATA4XX;
-                  addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
-                 break;
-        }
+    if (!ok && errno == 0) switch(buffer[0])
+      {
+      case '2': prdr_active = FALSE;
+               ok = TRUE;
+               break;
+      case '4': errno = ERRNO_DATA4XX;
+               addrlist->more_errno |=
+                 ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+               break;
+      }
     }
   else
 #endif
@@ -2569,7 +2634,9 @@ else
     int delivery_time = (int)(time(NULL) - start_delivery_time);
     int len;
     uschar *conf = NULL;
+
     send_rset = FALSE;
+    pipelining_active = FALSE;
 
     /* Set up confirmation if needed - applies only to SMTP */
 
@@ -2649,6 +2716,7 @@ else
 #ifndef DISABLE_PRDR
       if (prdr_active) addr->flags |= af_prdr_used;
 #endif
+      if (peer_offered & PEER_OFFERED_CHUNKING) addr->flags |= af_chunking_used;
       flag = '-';
 
 #ifndef DISABLE_PRDR
@@ -2975,6 +3043,7 @@ if (completed_address && ok && send_quit)
       if (tls_out.active >= 0)
         {
         tls_close(FALSE, TRUE);
+       smtp_peer_options = smtp_peer_options_wrap;
         if (smtps)
           ok = FALSE;
         else