+/* 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("----------- %s cutthrough setup ------------\n",
+ rcpt_count > 1 ? "more" : "start");
+(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';
+}
+
+
+/* fd and use_crlf args only to match write_chunk() */
+static BOOL
+cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf)
+{
+uschar * s2;
+while(s && (s2 = Ustrchr(s, '\n')))
+ {
+ if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl())
+ return FALSE;
+ s = s2+1;
+ }
+return TRUE;
+}
+
+
+/* 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)
+{
+if(cutthrough.fd < 0)
+ return FALSE;
+
+/* We share a routine with the mainline transport to handle header add/remove/rewrites,
+ but having a separate buffered-output function (for now)
+*/
+HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n");
+
+if (!transport_headers_send(&cutthrough.addr, cutthrough.fd,
+ cutthrough.addr.transport->add_headers,
+ cutthrough.addr.transport->remove_headers,
+ &cutthrough_write_chunk, TRUE,
+ cutthrough.addr.transport->rewrite_rules,
+ cutthrough.addr.transport->rewrite_existflags))
+ return FALSE;
+
+HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
+return TRUE;
+}
+
+
+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)
+{
+uschar res;
+address_item * addr;
+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;
+
+res = cutthrough_response('2', &cutthrough.addr.message);
+for (addr = &cutthrough.addr; addr; addr = addr->next)
+ {
+ addr->message = cutthrough.addr.message;
+ switch(res)
+ {
+ case '2':
+ delivery_log(LOG_MAIN, addr, (int)'>', NULL);
+ close_cutthrough_connection("delivered");
+ break;
+
+ case '4':
+ delivery_log(LOG_MAIN, addr, 0,
+ US"tmp-reject from cutthrough after DATA:");
+ break;
+
+ case '5':
+ delivery_log(LOG_MAIN|LOG_REJECT, addr, 0,
+ US"rejected after DATA:");
+ break;
+
+ default:
+ break;
+ }
+ }
+return cutthrough.addr.message;
+}
+
+
+