+/* Called after recipient-acl to get a cutthrough connection open when
+ one was requested and a recipient-verify wasn't subsequently done.
+*/
+void
+open_cutthrough_connection( address_item * addr )
+{
+address_item addr2;
+
+/* Use a recipient-verify-callout to set up the cutthrough connection. */
+/* We must use a copy of the address for verification, because it might
+get rewritten. */
+
+addr2 = *addr;
+HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n");
+(void) verify_address(&addr2, NULL,
+ vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
+ CUTTHROUGH_CMD_TIMEOUT, -1, -1,
+ NULL, NULL, NULL);
+HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n");
+return;
+}
+
+
+
+/* Send given number of bytes from the buffer */
+static BOOL
+cutthrough_send(int n)
+{
+if(cutthrough_fd < 0)
+ return TRUE;
+
+if(
+#ifdef SUPPORT_TLS
+ (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+#endif
+ send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+ )
+{
+ transport_count += n;
+ ctblock.ptr= ctblock.buffer;
+ return TRUE;
+}
+
+HDEBUG(D_transport|D_acl) debug_printf("cutthrough_send failed: %s\n", strerror(errno));
+return FALSE;
+}
+
+
+
+static BOOL
+_cutthrough_puts(uschar * cp, int n)
+{
+while(n--)
+ {
+ if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
+ if(!cutthrough_send(ctblock.buffersize))
+ return FALSE;
+
+ *ctblock.ptr++ = *cp++;
+ }
+return TRUE;
+}
+
+/* Buffered output of counted data block. Return boolean success */
+BOOL
+cutthrough_puts(uschar * cp, int n)
+{
+if (cutthrough_fd < 0) return TRUE;
+if (_cutthrough_puts(cp, n)) return TRUE;
+cancel_cutthrough_connection("transmit failed");
+return FALSE;
+}
+
+
+static BOOL
+_cutthrough_flush_send( void )
+{
+int n= ctblock.ptr-ctblock.buffer;
+
+if(n>0)
+ if(!cutthrough_send(n))
+ return FALSE;
+return TRUE;
+}
+
+
+/* Send out any bufferred output. Return boolean success. */
+BOOL
+cutthrough_flush_send( void )
+{
+if (_cutthrough_flush_send()) return TRUE;
+cancel_cutthrough_connection("transmit failed");
+return FALSE;
+}
+
+
+BOOL
+cutthrough_put_nl( void )
+{
+return cutthrough_puts(US"\r\n", 2);
+}
+
+
+/* Get and check response from cutthrough target */
+static uschar
+cutthrough_response(char expect, uschar ** copy)
+{
+smtp_inblock inblock;
+uschar inbuffer[4096];
+uschar responsebuffer[4096];
+
+inblock.buffer = inbuffer;
+inblock.buffersize = sizeof(inbuffer);
+inblock.ptr = inbuffer;
+inblock.ptrend = inbuffer;
+inblock.sock = cutthrough_fd;
+/* this relies on (inblock.sock == tls_out.active) */
+if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
+ cancel_cutthrough_connection("target timeout on read");
+
+if(copy != NULL)
+ {
+ uschar * cp;
+ *copy= cp= string_copy(responsebuffer);
+ /* Trim the trailing end of line */
+ cp += Ustrlen(responsebuffer);
+ if(cp > *copy && cp[-1] == '\n') *--cp = '\0';
+ if(cp > *copy && cp[-1] == '\r') *--cp = '\0';
+ }
+
+return responsebuffer[0];
+}
+
+
+/* Negotiate dataphase with the cutthrough target, returning success boolean */
+BOOL
+cutthrough_predata( void )
+{
+if(cutthrough_fd < 0)
+ return FALSE;
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n");
+cutthrough_puts(US"DATA\r\n", 6);
+cutthrough_flush_send();
+
+/* Assume nothing buffered. If it was it gets ignored. */
+return cutthrough_response('3', NULL) == '3';
+}
+
+
+/* Buffered send of headers. Return success boolean. */
+/* Expands newlines to wire format (CR,NL). */
+/* Also sends header-terminating blank line. */
+BOOL
+cutthrough_headers_send( void )
+{
+header_line * h;
+uschar * cp1, * cp2;
+
+if(cutthrough_fd < 0)
+ return FALSE;
+
+for(h= header_list; h != NULL; h= h->next)
+ if(h->type != htype_old && h->text != NULL)
+ for (cp1 = h->text; *cp1 && (cp2 = Ustrchr(cp1, '\n')); cp1 = cp2+1)
+ if( !cutthrough_puts(cp1, cp2-cp1)
+ || !cutthrough_put_nl())
+ return FALSE;
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>>(nl)\n");
+return cutthrough_put_nl();
+}
+
+
+static void
+close_cutthrough_connection( const char * why )
+{
+if(cutthrough_fd >= 0)
+ {
+ /* We could be sending this after a bunch of data, but that is ok as
+ the only way to cancel the transfer in dataphase is to drop the tcp
+ conn before the final dot.
+ */
+ ctblock.ptr = ctbuffer;
+ HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> QUIT\n");
+ _cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */
+ _cutthrough_flush_send();
+ /* No wait for response */
+
+ #ifdef SUPPORT_TLS
+ tls_close(FALSE, TRUE);
+ #endif
+ (void)close(cutthrough_fd);
+ cutthrough_fd= -1;
+ HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why);
+ }
+ctblock.ptr = ctbuffer;
+}
+
+void
+cancel_cutthrough_connection( const char * why )
+{
+close_cutthrough_connection(why);
+cutthrough_delivery= FALSE;
+}
+
+
+
+
+/* Have senders final-dot. Send one to cutthrough target, and grab the response.
+ Log an OK response as a transmission.
+ Close the connection.
+ Return smtp response-class digit.
+*/
+uschar *
+cutthrough_finaldot( void )
+{
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> .\n");
+
+/* Assume data finshed with new-line */
+if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send())
+ return cutthrough_addr.message;
+
+switch(cutthrough_response('2', &cutthrough_addr.message))
+ {
+ case '2':
+ delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
+ close_cutthrough_connection("delivered");
+ break;
+
+ case '4':
+ delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:");
+ break;
+
+ case '5':
+ delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:");
+ break;
+
+ default:
+ break;
+ }
+ return cutthrough_addr.message;
+}
+
+
+