+ /* Error on first TLS read */
+
+ else if (errno == ERRNO_TLSFAILURE)
+ return -5;
+
+ /* Timeout while reading the response */
+
+ else if (errno == ETIMEDOUT)
+ {
+ uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
+ transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
+ set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE, &sx->delivery_start);
+ retry_add_item(addr, addr->address_retry_key, 0);
+ update_waiting = FALSE;
+ return -1;
+ }
+
+ /* Handle other errors in obtaining an SMTP response by returning -1. This
+ will cause all the addresses to be deferred. Restore the SMTP command in
+ big_buffer for which we are checking the response, so the error message
+ makes sense. */
+
+ else if (errno != 0 || sx->buffer[0] == 0)
+ {
+ gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer }, * g = &gs;
+
+ /* Use taint-unchecked routines for writing into big_buffer, trusting
+ that we'll never expand it. */
+
+ g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>",
+ transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
+ string_from_gstring(g);
+ return -2;
+ }
+
+ /* Handle SMTP permanent and temporary response codes. */
+
+ else
+ {
+ addr->message =
+ string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
+ "%s", transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes),
+ string_printing(sx->buffer));
+ setflag(addr, af_pass_message);
+ if (!sx->verify)
+ msglog_line(sx->conn_args.host, addr->message);
+
+ /* The response was 5xx */
+
+ if (sx->buffer[0] == '5')
+ {
+ addr->transport_return = FAIL;
+ yield |= 2;
+ }
+
+ /* The response was 4xx */
+
+ else
+ {
+ addr->transport_return = DEFER;
+ addr->basic_errno = ERRNO_RCPT4XX;
+ addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+
+ if (!sx->verify)
+ {
+#ifndef DISABLE_EVENT
+ event_defer_errno = addr->more_errno;
+ msg_event_raise(US"msg:rcpt:host:defer", addr);
+#endif
+ /* If a 452 and we've had at least one 2xx or 5xx, set next_addr to the
+ start point for another MAIL command. */
+
+ if (addr->more_errno >> 8 == 52 && yield & 3)
+ {
+ if (!sx->RCPT_452) /* initialised at MAIL-ack above */
+ {
+ DEBUG(D_transport)
+ debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__);
+ sx->RCPT_452 = TRUE;
+ sx->next_addr = addr;
+ }
+ addr->transport_return = PENDING_DEFER;
+ addr->basic_errno = 0;
+ }
+ else
+ {
+ /* Log temporary errors if there are more hosts to be tried.
+ If not, log this last one in the == line. */
+
+ if (sx->conn_args.host->next)
+ if (LOGGING(outgoing_port))
+ log_write(0, LOG_MAIN, "H=%s [%s]:%d %s", sx->conn_args.host->name,
+ sx->conn_args.host->address,
+ sx->port == PORT_NONE ? 25 : sx->port, addr->message);
+ else
+ log_write(0, LOG_MAIN, "H=%s [%s]: %s", sx->conn_args.host->name,
+ sx->conn_args.host->address, addr->message);
+
+#ifndef DISABLE_EVENT
+ else
+ msg_event_raise(US"msg:rcpt:defer", addr);
+#endif
+
+ /* Do not put this message on the list of those waiting for specific
+ hosts, as otherwise it is likely to be tried too often. */
+
+ update_waiting = FALSE;
+
+ /* Add a retry item for the address so that it doesn't get tried again
+ too soon. If address_retry_include_sender is true, add the sender address
+ to the retry key. */
+
+ retry_add_item(addr,
+ ob->address_retry_include_sender
+ ? string_sprintf("%s:<%s>", addr->address_retry_key, sender_address)
+ : addr->address_retry_key,
+ 0);
+ }
+ }
+ }
+ }
+ if (count && !(addr = addr->next))
+ return -2;
+ } /* Loop for next RCPT response */
+
+/* Update where to start at for the next block of responses, unless we
+have already handled all the addresses. */
+
+if (addr) sx->sync_addr = addr->next;
+
+/* Handle a response to DATA. If we have not had any good recipients, either
+previously or in this block, the response is ignored. */
+
+if (pending_DATA != 0)
+ {
+ DEBUG(D_transport) debug_printf("%s expect data\n", __FUNCTION__);
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+ '3', ob->command_timeout))
+ {
+ int code;
+ uschar *msg;
+ BOOL pass_message;
+
+ if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */
+ return -5;
+
+ if (pending_DATA > 0 || (yield & 1) != 0)
+ {
+ if (errno == 0 && sx->buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX;
+ sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+ }
+ return -3;
+ }
+ (void)check_response(sx->conn_args.host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
+ DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
+ "is in use and there were no good recipients\n", msg);
+ }
+ }
+
+/* All responses read and handled; MAIL (if present) received 2xx and DATA (if
+present) received 3xx. If any RCPTs were handled and yielded anything other
+than 4xx, yield will be set non-zero. */
+
+return yield;
+}
+
+
+
+
+
+/* Try an authenticator's client entry */
+
+static int
+try_authenticator(smtp_context * sx, auth_instance * au)
+{
+smtp_transport_options_block * ob = sx->conn_args.ob; /* transport options */
+host_item * host = sx->conn_args.host; /* host to deliver to */
+int rc;
+
+sx->outblock.authenticating = TRUE;
+rc = (au->info->clientcode)(au, sx, ob->command_timeout,
+ sx->buffer, sizeof(sx->buffer));
+sx->outblock.authenticating = FALSE;
+DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
+
+/* A temporary authentication failure must hold up delivery to
+this host. After a permanent authentication failure, we carry on
+to try other authentication methods. If all fail hard, try to
+deliver the message unauthenticated unless require_auth was set. */
+
+switch(rc)
+ {
+ case OK:
+ f.smtp_authenticated = TRUE; /* stops the outer loop */
+ client_authenticator = au->name;
+ if (au->set_client_id)
+ client_authenticated_id = expand_string(au->set_client_id);
+ break;
+
+ /* Failure after writing a command */
+
+ case FAIL_SEND:
+ return FAIL_SEND;
+
+ /* Failure after reading a response */
+
+ case FAIL:
+ if (errno != 0 || sx->buffer[0] != '5') return FAIL;
+ log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+ au->name, host->name, host->address, sx->buffer);
+ break;
+
+ /* Failure by some other means. In effect, the authenticator
+ decided it wasn't prepared to handle this case. Typically this
+ is the result of "fail" in an expansion string. Do we need to
+ log anything here? Feb 2006: a message is now put in the buffer
+ if logging is required. */
+
+ case CANCELLED:
+ if (*sx->buffer != 0)
+ log_write(0, LOG_MAIN, "%s authenticator cancelled "
+ "authentication H=%s [%s] %s", au->name, host->name,
+ host->address, sx->buffer);
+ break;
+
+ /* Internal problem, message in buffer. */
+
+ case ERROR:
+ set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(sx->buffer),
+ DEFER, FALSE, &sx->delivery_start);
+ return ERROR;
+ }
+return OK;
+}
+
+
+
+
+/* Do the client side of smtp-level authentication.
+
+Arguments:
+ sx smtp connection context
+
+sx->buffer should have the EHLO response from server (gets overwritten)
+
+Returns:
+ OK Success, or failed (but not required): global "smtp_authenticated" set
+ DEFER Failed authentication (and was required)
+ ERROR Internal problem
+
+ FAIL_SEND Failed communications - transmit
+ FAIL - response
+*/
+
+static int
+smtp_auth(smtp_context * sx)
+{
+host_item * host = sx->conn_args.host; /* host to deliver to */
+smtp_transport_options_block * ob = sx->conn_args.ob; /* transport options */
+int require_auth = verify_check_given_host(CUSS &ob->hosts_require_auth, host);
+#ifndef DISABLE_PIPE_CONNECT
+unsigned short authbits = tls_out.active.sock >= 0
+ ? sx->ehlo_resp.crypted_auths : sx->ehlo_resp.cleartext_auths;
+#endif
+uschar * fail_reason = US"server did not advertise AUTH support";
+
+f.smtp_authenticated = FALSE;
+client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
+
+if (!regex_AUTH)
+ regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+
+/* Is the server offering AUTH? */
+
+if ( sx->esmtp
+ &&
+#ifndef DISABLE_PIPE_CONNECT
+ sx->early_pipe_active ? authbits
+ :
+#endif
+ regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)
+ )
+ {
+ uschar * names = NULL;
+ expand_nmax = -1; /* reset */
+
+#ifndef DISABLE_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ names = string_copyn(expand_nstring[1], expand_nlength[1]);
+
+ /* Must not do this check until after we have saved the result of the
+ regex match above as the check could be another RE. */
+
+ if ( require_auth == OK
+ || verify_check_given_host(CUSS &ob->hosts_try_auth, host) == OK)
+ {
+ DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+ fail_reason = US"no common mechanisms were found";
+
+#ifndef DISABLE_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ {
+ /* Scan our authenticators (which support use by a client and were offered
+ by the server (checked at cache-write time)), not suppressed by
+ client_condition. If one is found, attempt to authenticate by calling its
+ client function. We are limited to supporting up to 16 authenticator
+ public-names by the number of bits in a short. */
+
+ auth_instance * au;
+ uschar bitnum;
+ int rc;
+
+ for (bitnum = 0, au = auths;
+ !f.smtp_authenticated && au && bitnum < 16;
+ bitnum++, au = au->next) if (authbits & BIT(bitnum))
+ {
+ if ( au->client_condition
+ && !expand_check_condition(au->client_condition, au->name,
+ US"client authenticator"))
+ {
+ DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+ au->name, "client_condition is false");
+ continue;
+ }
+
+ /* Found data for a listed mechanism. Call its client entry. Set
+ a flag in the outblock so that data is overwritten after sending so
+ that reflections don't show it. */
+
+ fail_reason = US"authentication attempt(s) failed";
+
+ if ((rc = try_authenticator(sx, au)) != OK)
+ return rc;
+ }
+ }
+ else
+#endif
+
+ /* Scan the configured authenticators looking for one which is configured
+ for use as a client, which is not suppressed by client_condition, and
+ whose name matches an authentication mechanism supported by the server.
+ If one is found, attempt to authenticate by calling its client function.
+ */
+
+ for (auth_instance * au = auths; !f.smtp_authenticated && au; au = au->next)
+ {
+ uschar *p = names;
+
+ if ( !au->client
+ || ( au->client_condition
+ && !expand_check_condition(au->client_condition, au->name,
+ US"client authenticator")))
+ {
+ DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+ au->name,
+ (au->client)? "client_condition is false" :
+ "not configured as a client");
+ continue;
+ }
+
+ /* Loop to scan supported server mechanisms */
+
+ while (*p)
+ {
+ int len = Ustrlen(au->public_name);
+ int rc;
+
+ while (isspace(*p)) p++;
+
+ if (strncmpic(au->public_name, p, len) != 0 ||
+ (p[len] != 0 && !isspace(p[len])))
+ {
+ while (*p != 0 && !isspace(*p)) p++;
+ continue;
+ }
+
+ /* Found data for a listed mechanism. Call its client entry. Set
+ a flag in the outblock so that data is overwritten after sending so
+ that reflections don't show it. */
+
+ fail_reason = US"authentication attempt(s) failed";
+
+ if ((rc = try_authenticator(sx, au)) != OK)
+ return rc;
+
+ break; /* If not authenticated, try next authenticator */
+ } /* Loop for scanning supported server mechanisms */
+ } /* Loop for further authenticators */
+ }
+ }
+
+/* If we haven't authenticated, but are required to, give up. */
+
+if (require_auth == OK && !f.smtp_authenticated)
+ {
+#ifndef DISABLE_PIPE_CONNECT
+ invalidate_ehlo_cache_entry(sx);
+#endif
+ set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
+ string_sprintf("authentication required but %s", fail_reason), DEFER,
+ FALSE, &sx->delivery_start);
+ return DEFER;
+ }
+
+return OK;
+}
+
+
+/* Construct AUTH appendix string for MAIL TO */
+/*
+Arguments
+ sx context for smtp connection
+ p point in sx->buffer to build string
+ addrlist chain of potential addresses to deliver
+
+Globals f.smtp_authenticated
+ client_authenticated_sender
+Return True on error, otherwise buffer has (possibly empty) terminated string
+*/
+
+static BOOL
+smtp_mail_auth_str(smtp_context * sx, uschar * p, address_item * addrlist)
+{
+smtp_transport_options_block * ob = sx->conn_args.ob;
+uschar * local_authenticated_sender = authenticated_sender;
+
+#ifdef notdef
+ debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n",
+ authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
+#endif
+
+if (ob->authenticated_sender)
+ {
+ uschar * new = expand_string(ob->authenticated_sender);
+ if (!new)
+ {
+ if (!f.expand_string_forcedfail)
+ {
+ uschar *message = string_sprintf("failed to expand "
+ "authenticated_sender: %s", expand_string_message);
+ set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
+ return TRUE;
+ }
+ }
+ else if (*new)
+ local_authenticated_sender = new;
+ }
+
+/* Add the authenticated sender address if present */
+
+if ( (f.smtp_authenticated || ob->authenticated_sender_force)
+ && local_authenticated_sender)
+ {
+ string_format_nt(p, sizeof(sx->buffer) - (p-sx->buffer), " AUTH=%s",
+ auth_xtextencode(local_authenticated_sender,
+ Ustrlen(local_authenticated_sender)));
+ client_authenticated_sender = string_copy(local_authenticated_sender);
+ }
+else
+ *p = 0;
+
+return FALSE;
+}
+
+
+
+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"";
+#ifndef DISABLE_TLS
+uschar * tlsc1 = US"";
+#endif
+uschar * save_sender_address = sender_address;
+uschar * local_identity = NULL;
+smtp_transport_options_block * ob = SOB 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);
+
+#ifndef DISABLE_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 = spool_sender_from_msgid(message_id)))
+ return FALSE;
+
+
+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);
+
+/* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
+
+#ifndef DISABLE_TLS
+if ( checks & OPTION_TLS
+ && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+#endif
+ checks &= ~OPTION_TLS;
+
+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)
+#endif
+ checks &= ~OPTION_PRDR;
+
+#ifdef SUPPORT_I18N
+if ( checks & OPTION_UTF8
+ && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+#endif
+ checks &= ~OPTION_UTF8;
+
+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;
+
+#ifndef DISABLE_PIPE_CONNECT
+if ( checks & OPTION_EARLY_PIPE
+ && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0,
+ PCRE_EOPT, NULL, 0) < 0)
+#endif
+ checks &= ~OPTION_EARLY_PIPE;
+
+/* debug_printf("%s: found 0x%04x\n", __FUNCTION__, checks); */
+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
+ DEFER TLS error on first read (EHLO-resp); errno set
+*/
+
+static int
+smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
+ unsigned flags)
+{
+smtp_transport_options_block * ob = SOB 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)
+ {
+#ifndef DISABLE_PIPE_CONNECT
+ BOOL new_conn = !!(sx->outblock.conn_args);
+#endif
+ if((cmd_count = smtp_write_command(sx,
+ 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 */
+#ifndef DISABLE_PIPE_CONNECT
+ /* That command write could have been the one that made the connection.
+ Copy the fd from the client conn ctx (smtp transport specific) to the
+ generic transport ctx. */
+
+ if (new_conn)
+ tctx->u.fd = sx->outblock.cctx->sock;
+#endif
+ }
+
+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 -5: errno = ERRNO_TLSFAILURE;
+ return DEFER;
+#ifndef DISABLE_PIPE_CONNECT
+ case -4: /* non-2xx for pipelined banner or EHLO */
+#endif
+ 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, 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:
+ sx 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)
+{
+smtp_transport_options_block * ob = sx->conn_args.tblock->options_block;
+BOOL pass_message = FALSE;
+uschar * message = NULL;
+int yield = OK;
+#ifndef DISABLE_TLS
+uschar * tls_errstr;
+#endif
+
+sx->conn_args.ob = ob;
+
+sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
+sx->smtps = strcmpic(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;
+#ifdef SUPPORT_DANE
+/* sx->conn_args.dane = FALSE; */
+sx->dane_required =
+ verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
+#endif
+#ifndef DISABLE_PIPE_CONNECT
+/* sx->early_pipe_active = sx->early_pipe_ok = FALSE; */
+/* sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0; */
+/* sx->pending_BANNER = sx->pending_EHLO = sx->pending_MAIL = FALSE; */
+#endif
+
+if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) sx->max_mail = 999999;
+if ((sx->max_rcpt = sx->conn_args.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 = 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; */
+/* sx->outblock.conn_args = NULL; */
+
+/* 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;
+#ifdef USE_OPENSSL
+tls_out.sni = NULL;
+#endif
+tls_out.ocsp = OCSP_NOT_REQ;
+#ifndef DISABLE_TLS_RESUME
+tls_out.resumption = 0;
+#endif
+tls_out.ver = NULL;
+
+/* 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);
+
+#ifdef DISABLE_TLS
+if (sx->smtps)
+ {
+ set_errno_nohost(sx->addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
+ DEFER, FALSE, &sx->delivery_start);
+ return ERROR;
+ }
+#else
+
+/* If we have a proxied TLS connection, check usability for this message */
+
+if (continue_hostname && continue_proxy_cipher)
+ {
+ int rc;
+ const uschar * sni = US"";
+
+# ifdef SUPPORT_DANE
+ /* Check if the message will be DANE-verified; if so force its SNI */
+
+ tls_out.dane_verified = FALSE;
+ smtp_port_for_connect(sx->conn_args.host, sx->port);
+ if ( sx->conn_args.host->dnssec == DS_YES
+ && ( sx->dane_required
+ || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
+ ) )
+ switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
+ {
+ case OK: sx->conn_args.dane = TRUE;
+ ob->tls_tempfail_tryclear = FALSE; /* force TLS */
+ ob->tls_sni = sx->conn_args.host->name; /* force SNI */
+ break;
+ case FAIL_FORCED: break;
+ default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+ string_sprintf("DANE error: tlsa lookup %s",
+ rc_to_string(rc)),
+ rc, FALSE, &sx->delivery_start);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", sx->dane_required
+ ? US"dane-required" : US"dnssec-invalid");
+# endif
+ return rc;
+ }
+# endif
+
+ /* If the SNI or the DANE status required for the new message differs from the
+ existing conn drop the connection to force a new one. */
+
+ if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni)))
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "<%s>: failed to expand transport's tls_sni value: %s",
+ sx->addrlist->address, expand_string_message);
+
+# ifdef SUPPORT_DANE
+ if ( (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)
+ && continue_proxy_dane == sx->conn_args.dane)
+ {
+ tls_out.sni = US sni;
+ if ((tls_out.dane_verified = continue_proxy_dane))
+ sx->conn_args.host->dnssec = DS_YES;
+ }
+# else
+ if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni))
+ tls_out.sni = US sni;
+# endif
+ else
+ {
+ DEBUG(D_transport)
+ debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
+
+ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n");
+ write(0, "QUIT\r\n", 6);
+ close(0);
+ continue_hostname = continue_proxy_cipher = NULL;
+ f.continue_more = FALSE;
+ continue_sequence = 1; /* Unfortunately, this process cannot affect success log
+ which is done by delivery proc. Would have to pass this
+ back through reporting pipe. */
+ }
+ }
+#endif /*!DISABLE_TLS*/