+typedef struct smtp_compare_s
+{
+ uschar *current_sender_address;
+ struct transport_instance *tblock;
+} smtp_compare_t;
+
+
+/* Create a unique string that identifies this message, it is based on
+sender_address, helo_data and tls_certificate if enabled.
+*/
+
+static uschar *
+smtp_local_identity(uschar * sender, struct transport_instance * tblock)
+{
+address_item * addr1;
+uschar * if1 = US"";
+uschar * helo1 = US"";
+#ifdef SUPPORT_TLS
+uschar * tlsc1 = US"";
+#endif
+uschar * save_sender_address = sender_address;
+uschar * local_identity = NULL;
+smtp_transport_options_block * ob =
+ (smtp_transport_options_block *)tblock->options_block;
+
+sender_address = sender;
+
+addr1 = deliver_make_addr (sender, TRUE);
+deliver_set_expansions(addr1);
+
+if (ob->interface)
+ if1 = expand_string(ob->interface);
+
+if (ob->helo_data)
+ helo1 = expand_string(ob->helo_data);
+
+#ifdef SUPPORT_TLS
+if (ob->tls_certificate)
+ tlsc1 = expand_string(ob->tls_certificate);
+local_identity = string_sprintf ("%s^%s^%s", if1, helo1, tlsc1);
+#else
+local_identity = string_sprintf ("%s^%s", if1, helo1);
+#endif
+
+deliver_set_expansions(NULL);
+sender_address = save_sender_address;
+
+return local_identity;
+}
+
+
+
+/* This routine is a callback that is called from transport_check_waiting.
+This function will evaluate the incoming message versus the previous
+message. If the incoming message is using a different local identity then
+we will veto this new message. */
+
+static BOOL
+smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare)
+{
+uschar * message_local_identity,
+ * current_local_identity,
+ * new_sender_address;
+
+current_local_identity =
+ smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
+
+if (!(new_sender_address = deliver_get_sender_address(message_id)))
+ return 0;
+
+message_local_identity =
+ smtp_local_identity(new_sender_address, s_compare->tblock);
+
+return Ustrcmp(current_local_identity, message_local_identity) == 0;
+}
+
+
+
+static unsigned
+ehlo_response(uschar * buf, unsigned checks)
+{
+size_t bsize = Ustrlen(buf);
+
+#ifdef SUPPORT_TLS
+if ( checks & OPTION_TLS
+ && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_TLS;
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if ( checks & OPTION_REQUIRETLS
+ && pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0)
+ checks &= ~OPTION_REQUIRETLS;
+# endif
+#endif
+
+if ( checks & OPTION_IGNQ
+ && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
+ PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_IGNQ;
+
+if ( checks & OPTION_CHUNKING
+ && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_CHUNKING;
+
+#ifndef DISABLE_PRDR
+if ( checks & OPTION_PRDR
+ && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_PRDR;
+#endif
+
+#ifdef SUPPORT_I18N
+if ( checks & OPTION_UTF8
+ && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_UTF8;
+#endif
+
+if ( checks & OPTION_DSN
+ && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_DSN;
+
+if ( checks & OPTION_PIPE
+ && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
+ PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_PIPE;
+
+if ( checks & OPTION_SIZE
+ && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+ checks &= ~OPTION_SIZE;
+
+return checks;
+}
+
+
+
+/* Callback for emitting a BDAT data chunk header.
+
+If given a nonzero size, first flush any buffered SMTP commands
+then emit the command.
+
+Reap previous SMTP command responses if requested, and always reap
+the response from a previous BDAT command.
+
+Args:
+ tctx transport context
+ chunk_size value for SMTP BDAT command
+ flags
+ tc_chunk_last add LAST option to SMTP BDAT command
+ tc_reap_prev reap response to previous SMTP commands
+
+Returns: OK or ERROR
+*/
+
+static int
+smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
+ unsigned flags)
+{
+smtp_transport_options_block * ob =
+ (smtp_transport_options_block *)(tctx->tblock->options_block);
+smtp_context * sx = tctx->smtp_context;
+int cmd_count = 0;
+int prev_cmd_count;
+
+/* Write SMTP chunk header command. If not reaping responses, note that
+there may be more writes (like, the chunk data) done soon. */
+
+if (chunk_size > 0)
+ {
+ if((cmd_count = smtp_write_command(&sx->outblock,
+ flags & tc_reap_prev ? SCMD_FLUSH : SCMD_MORE,
+ "BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "")
+ ) < 0) return ERROR;
+ if (flags & tc_chunk_last)
+ data_command = string_copy(big_buffer); /* Save for later error message */
+ }
+
+prev_cmd_count = cmd_count += sx->cmd_count;
+
+/* Reap responses for any previous, but not one we just emitted */
+
+if (chunk_size > 0)
+ prev_cmd_count--;
+if (sx->pending_BDAT)
+ prev_cmd_count--;
+
+if (flags & tc_reap_prev && prev_cmd_count > 0)
+ {
+ DEBUG(D_transport) debug_printf("look for %d responses"
+ " for previous pipelined cmds\n", prev_cmd_count);
+
+ switch(sync_responses(sx, prev_cmd_count, 0))
+ {
+ case 1: /* 2xx (only) => OK */
+ case 3: sx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */
+ case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
+ case 0: break; /* No 2xx or 5xx, but no probs */
+
+ case -1: /* Timeout on RCPT */
+ default: return ERROR; /* I/O error, or any MAIL/DATA error */
+ }
+ cmd_count = 1;
+ if (!sx->pending_BDAT)
+ pipelining_active = FALSE;
+ }
+
+/* Reap response for an outstanding BDAT */
+
+if (sx->pending_BDAT)
+ {
+ DEBUG(D_transport) debug_printf("look for one response for BDAT\n");
+
+ if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+ ob->command_timeout))
+ {
+ if (errno == 0 && sx->buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX; /*XXX does this actually get used? */
+ sx->addrlist->more_errno |=
+ ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+ }
+ return ERROR;
+ }
+ cmd_count--;
+ sx->pending_BDAT = FALSE;
+ pipelining_active = FALSE;
+ }
+else if (chunk_size > 0)
+ sx->pending_BDAT = TRUE;
+
+
+sx->cmd_count = cmd_count;
+return OK;
+}
+
+
+
+/*************************************************
+* Make connection for given message *
+*************************************************/
+
+/*
+Arguments:
+ ctx connection context
+ suppress_tls if TRUE, don't attempt a TLS connection - this is set for
+ a second attempt after TLS initialization fails
+
+Returns: OK - the connection was made and the delivery attempted;
+ fd is set in the conn context, tls_out set up.
+ DEFER - the connection could not be made, or something failed
+ while setting up the SMTP session, or there was a
+ non-message-specific error, such as a timeout.
+ ERROR - helo_data or add_headers or authenticated_sender is
+ specified for this transport, and the string failed
+ to expand
+*/
+int
+smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
+{
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+dns_answer tlsa_dnsa;
+#endif
+BOOL pass_message = FALSE;
+uschar * message = NULL;
+int yield = OK;
+int rc;
+
+sx->ob = (smtp_transport_options_block *) sx->tblock->options_block;
+
+sx->lmtp = strcmpic(sx->ob->protocol, US"lmtp") == 0;
+sx->smtps = strcmpic(sx->ob->protocol, US"smtps") == 0;
+sx->ok = FALSE;
+sx->send_rset = TRUE;
+sx->send_quit = TRUE;
+sx->setting_up = TRUE;
+sx->esmtp = TRUE;
+sx->esmtp_sent = FALSE;
+#ifdef SUPPORT_I18N
+sx->utf8_needed = FALSE;
+#endif
+sx->dsn_all_lasthop = TRUE;
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+sx->dane = FALSE;
+sx->dane_required =
+ verify_check_given_host(&sx->ob->hosts_require_dane, sx->host) == OK;
+#endif
+
+if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
+sx->peer_offered = 0;
+sx->avoid_option = 0;
+sx->igquotstr = US"";
+if (!sx->helo_data) sx->helo_data = sx->ob->helo_data;
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->smtp_greeting = NULL;
+sx->helo_response = NULL;
+#endif
+
+smtp_command = US"initial connection";
+sx->buffer[0] = '\0';
+
+/* Set up the buffer for reading SMTP response packets. */
+
+sx->inblock.buffer = sx->inbuffer;
+sx->inblock.buffersize = sizeof(sx->inbuffer);
+sx->inblock.ptr = sx->inbuffer;
+sx->inblock.ptrend = sx->inbuffer;
+
+/* Set up the buffer for holding SMTP commands while pipelining */
+
+sx->outblock.buffer = sx->outbuffer;
+sx->outblock.buffersize = sizeof(sx->outbuffer);
+sx->outblock.ptr = sx->outbuffer;
+sx->outblock.cmd_count = 0;
+sx->outblock.authenticating = FALSE;
+
+/* Reset the parameters of a TLS session. */
+
+tls_out.bits = 0;
+tls_out.cipher = NULL; /* the one we may use for this transport */
+tls_out.ourcert = NULL;
+tls_out.peercert = NULL;
+tls_out.peerdn = NULL;
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+tls_out.sni = NULL;
+#endif
+tls_out.ocsp = OCSP_NOT_REQ;
+
+/* Flip the legacy TLS-related variables over to the outbound set in case
+they're used in the context of the transport. Don't bother resetting
+afterward (when being used by a transport) as we're in a subprocess.
+For verify, unflipped once the callout is dealt with */
+
+tls_modify_variables(&tls_out);
+
+#ifndef SUPPORT_TLS
+if (sx->smtps)
+ {
+ set_errno_nohost(sx->addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
+ DEFER, FALSE);
+ return ERROR;
+ }
+#endif
+
+/* Make a connection to the host if this isn't a continued delivery, and handle
+the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
+specially so they can be identified for retries. */
+
+if (!continue_hostname)
+ {
+ if (sx->verify)
+ HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
+
+ /* Get the actual port the connection will use, into sx->host */
+
+ smtp_port_for_connect(sx->host, sx->port);
+
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+ /* Do TLSA lookup for DANE */
+ {
+ tls_out.dane_verified = FALSE;
+ tls_out.tlsa_usage = 0;
+
+ if (sx->host->dnssec == DS_YES)
+ {
+ if( sx->dane_required
+ || verify_check_given_host(&sx->ob->hosts_try_dane, sx->host) == OK
+ )
+ switch (rc = tlsa_lookup(sx->host, &tlsa_dnsa, sx->dane_required))
+ {
+ case OK: sx->dane = TRUE;
+ sx->ob->tls_tempfail_tryclear = FALSE;
+ break;
+ case FAIL_FORCED: break;
+ default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+ string_sprintf("DANE error: tlsa lookup %s",
+ rc == DEFER ? "DEFER" : "FAIL"),
+ rc, FALSE);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->tblock->event_action,
+ US"dane:fail", sx->dane_required
+ ? US"dane-required" : US"dnssec-invalid");
+# endif
+ return rc;
+ }
+ }
+ else if (sx->dane_required)
+ {
+ set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+ string_sprintf("DANE error: %s lookup not DNSSEC", sx->host->name),
+ FAIL, FALSE);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->tblock->event_action,
+ US"dane:fail", US"dane-required");
+# endif
+ return FAIL;
+ }
+ }
+#endif /*DANE*/
+
+ /* Make the TCP connection */
+
+ sx->cctx.sock =
+ smtp_connect(sx->host, sx->host_af, sx->interface,
+ sx->ob->connect_timeout, sx->tblock);
+ sx->cctx.tls_ctx = NULL;
+ sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+
+ if (sx->cctx.sock < 0)
+ {
+ uschar * msg = NULL;
+ if (sx->verify)
+ {
+ msg = US strerror(errno);
+ HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+ }
+ set_errno_nohost(sx->addrlist,
+ errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+ sx->verify ? string_sprintf("could not connect: %s", msg)
+ : NULL,
+ DEFER, FALSE);
+ sx->send_quit = FALSE;
+ return DEFER;
+ }
+
+ /* Expand the greeting message while waiting for the initial response. (Makes
+ sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
+ delayed till here so that $sending_interface and $sending_port are set. */
+
+ if (sx->helo_data)
+ if (!(sx->helo_data = expand_string(sx->helo_data)))
+ if (sx->verify)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "<%s>: failed to expand transport's helo_data value for callout: %s",
+ sx->addrlist->address, expand_string_message);
+
+#ifdef SUPPORT_I18N
+ if (sx->helo_data)
+ {
+ expand_string_message = NULL;
+ if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data,
+ &expand_string_message)),
+ expand_string_message)
+ if (sx->verify)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "<%s>: failed to expand transport's helo_data value for callout: %s",
+ sx->addrlist->address, expand_string_message);
+ else
+ sx->helo_data = NULL;
+ }
+#endif
+
+ /* The first thing is to wait for an initial OK response. The dreaded "goto"
+ is nevertheless a reasonably clean way of programming this kind of logic,
+ where you want to escape on any error. */
+
+ if (!sx->smtps)
+ {
+ BOOL good_response;
+
+#ifdef TCP_QUICKACK
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+ good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ '2', sx->ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+ sx->smtp_greeting = string_copy(sx->buffer);
+#endif
+ if (!good_response) goto RESPONSE_FAILED;
+
+#ifndef DISABLE_EVENT
+ {
+ uschar * s;
+ lookup_dnssec_authenticated = sx->host->dnssec==DS_YES ? US"yes"
+ : sx->host->dnssec==DS_NO ? US"no" : NULL;
+ s = event_raise(sx->tblock->event_action, US"smtp:connect", sx->buffer);
+ if (s)
+ {
+ set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL,
+ string_sprintf("deferred by smtp:connect event expansion: %s", s),
+ DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ }
+#endif
+
+ /* Now check if the helo_data expansion went well, and sign off cleanly if
+ it didn't. */
+
+ if (!sx->helo_data)
+ {
+ message = string_sprintf("failed to expand helo_data: %s",
+ expand_string_message);
+ set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ }
+
+/** Debugging without sending a message
+sx->addrlist->transport_return = DEFER;
+goto SEND_QUIT;
+**/
+
+ /* Errors that occur after this point follow an SMTP command, which is
+ left in big_buffer by smtp_write_command() for use in error messages. */
+
+ smtp_command = big_buffer;
+
+ /* Tell the remote who we are...