+ /* Expect another BDAT cmd from input. RFC 3030 says nothing about
+ QUIT, RSET or NOOP but handling them seems obvious */
+
+next_cmd:
+ switch(smtp_read_command(TRUE, 1))
+ {
+ default:
+ (void) synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"only BDAT permissible after non-LAST BDAT");
+
+ repeat_until_rset:
+ switch(smtp_read_command(TRUE, 1))
+ {
+ case QUIT_CMD: smtp_quit_handler(&user_msg, &log_msg); /*FALLTHROUGH */
+ case EOF_CMD: return EOF;
+ case RSET_CMD: smtp_rset_handler(); return ERR;
+ default: if (synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"only RSET accepted now") > 0)
+ return EOF;
+ goto repeat_until_rset;
+ }
+
+ case QUIT_CMD:
+ smtp_quit_handler(&user_msg, &log_msg);
+ /*FALLTHROUGH*/
+ case EOF_CMD:
+ return EOF;
+
+ case RSET_CMD:
+ smtp_rset_handler();
+ return ERR;
+
+ case NOOP_CMD:
+ HAD(SCH_NOOP);
+ smtp_printf("250 OK\r\n", FALSE);
+ goto next_cmd;
+
+ case BDAT_CMD:
+ {
+ int n;
+
+ if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
+ {
+ (void) synprot_error(L_smtp_protocol_error, 501, NULL,
+ US"missing size for BDAT command");
+ return ERR;
+ }
+ chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
+ ? CHUNKING_LAST : CHUNKING_ACTIVE;
+ chunking_data_left = chunking_datasize;
+ DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+ (int)chunking_state, chunking_data_left);
+
+ if (chunking_datasize == 0)
+ if (chunking_state == CHUNKING_LAST)
+ return EOD;
+ else
+ {
+ (void) synprot_error(L_smtp_protocol_error, 504, NULL,
+ US"zero size for BDAT command");
+ goto repeat_until_rset;
+ }
+
+ bdat_push_receive_functions();
+#ifndef DISABLE_DKIM
+ dkim_collect_input = dkim_save;
+#endif
+ break; /* to top of main loop */
+ }
+ }
+ }
+}
+
+BOOL
+bdat_hasc(void)
+{
+if (chunking_data_left > 0)
+ return lwr_receive_hasc();
+return TRUE;
+}
+
+uschar *
+bdat_getbuf(unsigned * len)
+{
+uschar * buf;
+
+if (chunking_data_left <= 0)
+ { *len = 0; return NULL; }
+
+if (*len > chunking_data_left) *len = chunking_data_left;
+buf = lwr_receive_getbuf(len); /* Either smtp_getbuf or tls_getbuf */
+chunking_data_left -= *len;
+return buf;
+}
+
+void
+bdat_flush_data(void)
+{
+while (chunking_data_left)
+ {
+ unsigned n = chunking_data_left;
+ if (!bdat_getbuf(&n)) break;
+ }
+
+bdat_pop_receive_functions();
+chunking_state = CHUNKING_OFFERED;
+DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
+}
+
+
+static inline void
+bdat_push_receive_functions(void)
+{
+/* push the current receive_* function on the "stack", and
+replace them by bdat_getc(), which in turn will use the lwr_receive_*
+functions to do the dirty work. */
+if (!lwr_receive_getc)
+ {
+ lwr_receive_getc = receive_getc;
+ lwr_receive_getbuf = receive_getbuf;
+ lwr_receive_hasc = receive_hasc;
+ lwr_receive_ungetc = receive_ungetc;
+ }
+else
+ {
+ DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
+ }
+
+receive_getc = bdat_getc;
+receive_getbuf = bdat_getbuf;
+receive_hasc = bdat_hasc;
+receive_ungetc = bdat_ungetc;
+}
+
+static inline void
+bdat_pop_receive_functions(void)
+{
+if (!lwr_receive_getc)
+ {
+ DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n");
+ return;
+ }
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+receive_hasc = lwr_receive_hasc;
+receive_ungetc = lwr_receive_ungetc;
+
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_hasc = NULL;
+lwr_receive_ungetc = NULL;
+}
+
+int
+bdat_ungetc(int ch)
+{
+chunking_data_left++;
+bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */
+return lwr_receive_ungetc(ch);
+}
+
+
+
+/******************************************************************************/
+
+/*************************************************
+* Write formatted string to SMTP channel *
+*************************************************/
+
+/* This is a separate function so that we don't have to repeat everything for
+TLS support or debugging. It is global so that the daemon and the
+authentication functions can use it. It does not return any error indication,
+because major problems such as dropped connections won't show up till an output
+flush for non-TLS connections. The smtp_fflush() function is available for
+checking that: for convenience, TLS output errors are remembered here so that
+they are also picked up later by smtp_fflush().
+
+This function is exposed to the local_scan API; do not change the signature.
+
+Arguments:
+ format format string
+ more further data expected
+ ... optional arguments
+
+Returns: nothing
+*/
+
+void
+smtp_printf(const char *format, BOOL more, ...)
+{
+va_list ap;
+
+va_start(ap, more);
+smtp_vprintf(format, more, ap);
+va_end(ap);
+}
+
+/* This is split off so that verify.c:respond_printf() can, in effect, call
+smtp_printf(), bearing in mind that in C a vararg function can't directly
+call another vararg function, only a function which accepts a va_list.
+
+This function is exposed to the local_scan API; do not change the signature.
+*/
+/*XXX consider passing caller-info in, for string_vformat-onward */
+
+void
+smtp_vprintf(const char *format, BOOL more, va_list ap)
+{
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
+BOOL yield;
+
+/* Use taint-unchecked routines for writing into big_buffer, trusting
+that we'll never expand it. */
+
+yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap);
+string_from_gstring(&gs);
+
+DEBUG(D_receive) for (const uschar * t, * s = gs.s;
+ s && (t = Ustrchr(s, '\r'));
+ s = t + 2) /* \r\n */
+ debug_printf("%s %.*s\n",
+ s == gs.s ? "SMTP>>" : " ",
+ (int)(t - s), s);
+
+if (!yield)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
+ smtp_closedown(US"Unexpected error");
+ exim_exit(EXIT_FAILURE);
+ }
+
+/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
+have had the same. Note: this code is also present in smtp_respond(). It would
+be tidier to have it only in one place, but when it was added, it was easier to
+do it that way, so as not to have to mess with the code for the RCPT command,
+which sometimes uses smtp_printf() and sometimes smtp_respond(). */
+
+if (fl.rcpt_in_progress)
+ {
+ if (!rcpt_smtp_response)
+ rcpt_smtp_response = string_copy(big_buffer);
+ else if (fl.rcpt_smtp_response_same &&
+ Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
+ fl.rcpt_smtp_response_same = FALSE;
+ fl.rcpt_in_progress = FALSE;
+ }
+
+/* Now write the string */
+
+if (
+#ifndef DISABLE_TLS
+ tls_in.active.sock >= 0 ? (tls_write(NULL, gs.s, gs.ptr, more) < 0) :
+#endif
+ (fwrite(gs.s, gs.ptr, 1, smtp_out) == 0)
+ )
+ smtp_write_error = -1;
+}
+
+
+
+/*************************************************
+* Flush SMTP out and check for error *
+*************************************************/
+
+/* This function isn't currently used within Exim (it detects errors when it
+tries to read the next SMTP input), but is available for use in local_scan().
+It flushes the output and checks for errors.
+
+Arguments: none
+Returns: 0 for no error; -1 after an error
+*/
+
+int
+smtp_fflush(void)
+{
+if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+
+if (
+#ifndef DISABLE_TLS
+ tls_in.active.sock >= 0 ? (tls_write(NULL, NULL, 0, FALSE) < 0) :
+#endif
+ (fflush(smtp_out) != 0)
+ )
+ smtp_write_error = -1;
+
+return smtp_write_error;
+}
+
+
+
+/* If there's input waiting (and we're doing pipelineing) then we can pipeline
+a reponse with the one following. */
+
+static BOOL
+pipeline_response(void)
+{
+if ( !smtp_enforce_sync || !sender_host_address
+ || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
+ return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_pipelining_used = TRUE;
+return TRUE;
+}
+
+
+#ifndef DISABLE_PIPE_CONNECT
+static BOOL
+pipeline_connect_sends(void)
+{
+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
+ return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_early_pipe_used = TRUE;
+return TRUE;
+}
+#endif
+
+/*************************************************
+* SMTP command read timeout *
+*************************************************/