+ if (chunking_state == CHUNKING_LAST)
+ {
+#ifndef DISABLE_DKIM
+ dkim_exim_verify_feed(NULL, 0); /* notify EOD */
+#endif
+ return EOD;
+ }
+
+ smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
+ chunking_state = CHUNKING_OFFERED;
+ DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
+
+ /* 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;
+ }
+
+ receive_getc = bdat_getc;
+ receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */
+ receive_ungetc = bdat_ungetc;
+#ifndef DISABLE_DKIM
+ dkim_collect_input = dkim_save;
+#endif
+ break; /* to top of main loop */
+ }
+ }
+ }
+}
+
+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;
+ }
+
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+receive_ungetc = lwr_receive_ungetc;
+
+if (chunking_state != CHUNKING_LAST)
+ {
+ chunking_state = CHUNKING_OFFERED;
+ DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
+ }
+}
+
+
+
+
+/*************************************************
+* SMTP version of ungetc() *
+*************************************************/
+
+/* Puts a character back in the input buffer. Only ever
+called once.
+
+Arguments:
+ ch the character
+
+Returns: the character
+*/
+
+int
+smtp_ungetc(int ch)
+{
+*--smtp_inptr = ch;
+return ch;
+}
+
+
+int
+bdat_ungetc(int ch)
+{
+chunking_data_left++;
+return lwr_receive_ungetc(ch);
+}
+
+
+
+/*************************************************
+* SMTP version of feof() *
+*************************************************/
+
+/* Tests for a previous EOF
+
+Arguments: none
+Returns: non-zero if the eof flag is set
+*/
+
+int
+smtp_feof(void)
+{
+return smtp_had_eof;
+}
+
+
+
+
+/*************************************************
+* SMTP version of ferror() *
+*************************************************/
+
+/* Tests for a previous read error, and returns with errno
+restored to what it was when the error was detected.
+
+Arguments: none
+Returns: non-zero if the error flag is set
+*/
+
+int
+smtp_ferror(void)
+{
+errno = smtp_had_error;
+return smtp_had_error;
+}
+
+
+
+/*************************************************
+* Test for characters in the SMTP buffer *
+*************************************************/
+
+/* Used at the end of a message
+
+Arguments: none
+Returns: TRUE/FALSE
+*/
+
+BOOL
+smtp_buffered(void)
+{
+return smtp_inptr < smtp_inend;
+}
+
+
+
+/*************************************************
+* 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)
+ {
+ uschar *msg_copy, *cr, *end;
+ msg_copy = string_copy(gs.s);
+ end = msg_copy + gs.ptr;
+ while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */
+ memmove(cr, cr + 1, (end--) - cr);
+ debug_printf("SMTP>> %s", msg_copy);
+ }
+
+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 == NULL)
+ 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