+/* forward declarations */
+static int smtp_read_command(BOOL check_sync, unsigned buffer_lim);
+static int synprot_error(int type, int code, uschar *data, uschar *errmess);
+static void smtp_quit_handler(uschar **, uschar **);
+static void smtp_rset_handler(void);
+
+/*************************************************
+* Recheck synchronization *
+*************************************************/
+
+/* Synchronization checks can never be perfect because a packet may be on its
+way but not arrived when the check is done. Normally, the checks happen when
+commands are read: Exim ensures that there is no more input in the input buffer.
+In normal cases, the response to the command will be fast, and there is no
+further check.
+
+However, for some commands an ACL is run, and that can include delays. In those
+cases, it is useful to do another check on the input just before sending the
+response. This also applies at the start of a connection. This function does
+that check by means of the select() function, as long as the facility is not
+disabled or inappropriate. A failure of select() is ignored.
+
+When there is unwanted input, we read it so that it appears in the log of the
+error.
+
+Arguments: none
+Returns: TRUE if all is well; FALSE if there is input pending
+*/
+
+static BOOL
+wouldblock_reading(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero;
+
+#ifndef DISABLE_TLS
+if (tls_in.active.sock >= 0)
+ return !tls_could_read();
+#endif
+
+if (smtp_inptr < smtp_inend)
+ return FALSE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+tzero.tv_sec = 0;
+tzero.tv_usec = 0;
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return TRUE; /* Not ready to read */
+rc = smtp_getc(GETC_BUFFER_UNLIMITED);
+if (rc < 0) return TRUE; /* End of file or error */
+
+smtp_ungetc(rc);
+return FALSE;
+}
+
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
+ return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/* 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;
+}
+
+
+#ifdef SUPPORT_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
+
+/*************************************************
+* Log incomplete transactions *
+*************************************************/
+
+/* This function is called after a transaction has been aborted by RSET, QUIT,
+connection drops or other errors. It logs the envelope information received
+so far in order to preserve address verification attempts.
+
+Argument: string to indicate what aborted the transaction
+Returns: nothing
+*/
+
+static void
+incomplete_transaction_log(uschar *what)
+{
+if (!sender_address /* No transaction in progress */
+ || !LOGGING(smtp_incomplete_transaction))
+ return;
+
+/* Build list of recipients for logging */
+
+if (recipients_count > 0)
+ {
+ raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE);
+ for (int i = 0; i < recipients_count; i++)
+ raw_recipients[i] = recipients_list[i].address;
+ raw_recipients_count = recipients_count;
+ }
+
+log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
+ "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
+}
+
+
+
+
+void
+smtp_command_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+ LOG_MAIN, "SMTP command timeout on%s connection from %s",
+ tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
+if (smtp_batched_input)
+ moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
+smtp_notquit_exit(US"command-timeout", US"421",
+ US"%s: SMTP command timeout - closing connection",
+ smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_command_sigterm_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+if (smtp_batched_input)
+ moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */
+smtp_notquit_exit(US"signal-exit", US"421",
+ US"%s: Service not available - closing connection", smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_data_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+ LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>",
+ sender_fullhost ? sender_fullhost : US"local process", sender_address);
+receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
+/* Does not return */
+}
+
+void
+smtp_data_sigint_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after %s",
+ smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
+receive_bomb_out(US"signal-exit",
+ US"Service not available - SIGTERM or SIGINT received");
+/* Does not return */
+}
+
+
+
+/* Refill the buffer, and notify DKIM verification code.
+Return false for error or EOF.
+*/
+
+static BOOL
+smtp_refill(unsigned lim)
+{
+int rc, save_errno;
+if (!smtp_out) return FALSE;
+fflush(smtp_out);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+
+/* Limit amount read, so non-message data is not fed to DKIM.
+Take care to not touch the safety NUL at the end of the buffer. */
+
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
+save_errno = errno;
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+if (rc <= 0)
+ {
+ /* Must put the error text in fixed store, because this might be during
+ header reading, where it releases unused store above the header. */
+ if (rc < 0)
+ {
+ if (had_command_timeout) /* set by signal handler */
+ smtp_command_timeout_exit(); /* does not return */
+ if (had_command_sigterm)
+ smtp_command_sigterm_exit();
+ if (had_data_timeout)
+ smtp_data_timeout_exit();
+ if (had_data_sigint)
+ smtp_data_sigint_exit();
+
+ smtp_had_error = save_errno;
+ smtp_read_error = string_copy_perm(
+ string_sprintf(" (error: %s)", strerror(save_errno)), FALSE);
+ }
+ else
+ smtp_had_eof = 1;
+ return FALSE;
+ }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(smtp_inbuffer, rc);
+#endif
+smtp_inend = smtp_inbuffer + rc;
+smtp_inptr = smtp_inbuffer;
+return TRUE;
+}
+