+ Exim originally sent "Helo" at this point and ran for nearly a year that way.
+ Then somebody tried it with a Microsoft mailer... It seems that all other
+ mailers use upper case for some reason (the RFC is quite clear about case
+ independence) so, for peace of mind, I gave in. */
+
+ sx->esmtp = verify_check_given_host(&sx->ob->hosts_avoid_esmtp, sx->host) != OK;
+
+ /* Alas; be careful, since this goto is not an error-out, so conceivably
+ we might set data between here and the target which we assume to exist
+ and be usable. I can see this coming back to bite us. */
+#ifdef SUPPORT_TLS
+ if (sx->smtps)
+ {
+ smtp_peer_options |= OPTION_TLS;
+ suppress_tls = FALSE;
+ sx->ob->tls_tempfail_tryclear = FALSE;
+ smtp_command = US"SSL-on-connect";
+ goto TLS_NEGOTIATE;
+ }
+#endif
+
+ if (sx->esmtp)
+ {
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "%s %s\r\n",
+ sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
+ goto SEND_FAILED;
+ sx->esmtp_sent = TRUE;
+ if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+ sx->ob->command_timeout))
+ {
+ if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
+ {
+#ifdef EXPERIMENTAL_DSN_INFO
+ sx->helo_response = string_copy(sx->buffer);
+#endif
+ goto RESPONSE_FAILED;
+ }
+ sx->esmtp = FALSE;
+ }
+#ifdef EXPERIMENTAL_DSN_INFO
+ sx->helo_response = string_copy(sx->buffer);
+#endif
+ }
+ else
+ DEBUG(D_transport)
+ debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+
+ if (!sx->esmtp)
+ {
+ BOOL good_response;
+ int n = sizeof(sx->buffer);
+ uschar * rsp = sx->buffer;
+
+ if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
+ { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
+ goto SEND_FAILED;
+ good_response = smtp_read_response(&sx->inblock, rsp, n,
+ '2', sx->ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+ sx->helo_response = string_copy(rsp);
+#endif
+ if (!good_response)
+ {
+ /* Handle special logging for a closed connection after HELO
+ when had previously sent EHLO */
+
+ if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+ {
+ errno = ERRNO_SMTPCLOSED;
+ goto EHLOHELO_FAILED;
+ }
+ memmove(sx->buffer, rsp, Ustrlen(rsp));
+ goto RESPONSE_FAILED;
+ }
+ }
+
+ sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
+
+ if (sx->esmtp || sx->lmtp)
+ {
+ sx->peer_offered = ehlo_response(sx->buffer,
+ OPTION_TLS /* others checked later */
+ );
+
+ /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
+
+#ifdef SUPPORT_TLS
+ smtp_peer_options |= sx->peer_offered & OPTION_TLS;
+#endif
+ }
+ }
+
+/* For continuing deliveries down the same channel, having re-exec'd the socket
+is the standard input; for a socket held open from verify it is recorded
+in the cutthrough context block. Either way we don't need to redo EHLO here
+(but may need to do so for TLS - see below).
+Set up the pointer to where subsequent commands will be left, for
+error messages. Note that smtp_peer_options will have been
+set from the command line if they were set in the process that passed the
+connection on. */
+
+/*XXX continue case needs to propagate DSN_INFO, prob. in deliver.c
+as the continue goes via transport_pass_socket() and doublefork and exec.
+It does not wait. Unclear how we keep separate host's responses
+separate - we could match up by host ip+port as a bodge. */
+
+else
+ {
+ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+ {
+ sx->cctx = cutthrough.cctx;
+ sx->host->port = sx->port = cutthrough.host.port;
+ }
+ else
+ {
+ sx->cctx.sock = 0; /* stdin */
+ sx->cctx.tls_ctx = NULL;
+ smtp_port_for_connect(sx->host, sx->port); /* Record the port that was used */
+ }
+ sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+ smtp_command = big_buffer;
+ sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */
+
+ /* For a continued connection with TLS being proxied for us, or a
+ held-open verify connection with TLS, nothing more to do. */
+
+ if ( continue_proxy_cipher
+ || (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only
+ && cutthrough.is_tls)
+ )
+ {
+ sx->peer_offered = smtp_peer_options;
+ sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
+ HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
+ continue_proxy_cipher ? "proxied" : "verify conn with");
+ return OK;
+ }
+ HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
+ }
+
+/* If TLS is available on this connection, whether continued or not, attempt to
+start up a TLS session, unless the host is in hosts_avoid_tls. If successful,
+send another EHLO - the server may give a different answer in secure mode. We
+use a separate buffer for reading the response to STARTTLS so that if it is
+negative, the original EHLO data is available for subsequent analysis, should
+the client not be required to use TLS. If the response is bad, copy the buffer
+for error analysis. */
+
+#ifdef SUPPORT_TLS
+if ( smtp_peer_options & OPTION_TLS
+ && !suppress_tls
+ && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK
+ && ( !sx->verify
+ || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK
+ ) )
+ {
+ uschar buffer2[4096];
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "STARTTLS\r\n") < 0)
+ goto SEND_FAILED;
+
+ /* If there is an I/O error, transmission of this message is deferred. If
+ there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is
+ false, we also defer. However, if there is a temporary rejection of STARTTLS
+ and tls_tempfail_tryclear is true, or if there is an outright rejection of
+ STARTTLS, we carry on. This means we will try to send the message in clear,
+ unless the host is in hosts_require_tls (tested below). */
+
+ if (!smtp_read_response(&sx->inblock, buffer2, sizeof(buffer2), '2',
+ sx->ob->command_timeout))
+ {
+ if ( errno != 0
+ || buffer2[0] == 0
+ || (buffer2[0] == '4' && !sx->ob->tls_tempfail_tryclear)
+ )
+ {
+ Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer));
+ sx->buffer[sizeof(sx->buffer)-1] = '\0';
+ goto RESPONSE_FAILED;
+ }
+ }
+
+ /* STARTTLS accepted: try to negotiate a TLS session. */
+
+ else
+ TLS_NEGOTIATE:
+ {
+ address_item * addr;
+ uschar * errstr;
+ sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->host,
+ sx->addrlist, sx->tblock,
+# ifdef SUPPORT_DANE
+ sx->dane ? &tlsa_dnsa : NULL,
+# endif
+ &tls_out, &errstr);
+
+ if (!sx->cctx.tls_ctx)
+ {
+ /* TLS negotiation failed; give an error. From outside, this function may
+ be called again to try in clear on a new connection, if the options permit
+ it for this host. */
+
+# ifdef SUPPORT_DANE
+ if (sx->dane)
+ {
+ log_write(0, LOG_MAIN,
+ "DANE attempt failed; TLS connection to %s [%s]: %s",
+ sx->host->name, sx->host->address, errstr);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->tblock->event_action,
+ US"dane:fail", US"validation-failure"); /* could do with better detail */
+# endif
+ }
+# endif
+
+ errno = ERRNO_TLSFAILURE;
+ message = string_sprintf("TLS session: %s", errstr);
+ sx->send_quit = FALSE;
+ goto TLS_FAILED;
+ }
+
+ /* TLS session is set up */
+
+ smtp_peer_options_wrap = smtp_peer_options;
+ for (addr = sx->addrlist; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ {
+ addr->cipher = tls_out.cipher;
+ addr->ourcert = tls_out.ourcert;
+ addr->peercert = tls_out.peercert;
+ addr->peerdn = tls_out.peerdn;
+ addr->ocsp = tls_out.ocsp;
+ }
+ }
+ }
+
+/* if smtps, we'll have smtp_command set to something else; always safe to
+reset it here. */
+smtp_command = big_buffer;
+
+/* If we started TLS, redo the EHLO/LHLO exchange over the secure channel. If
+helo_data is null, we are dealing with a connection that was passed from
+another process, and so we won't have expanded helo_data above. We have to
+expand it here. $sending_ip_address and $sending_port are set up right at the
+start of the Exim process (in exim.c). */
+
+if (tls_out.active.sock >= 0)
+ {
+ char *greeting_cmd;
+ BOOL good_response;
+
+ if (!sx->helo_data && !(sx->helo_data = expand_string(sx->ob->helo_data)))
+ {
+ uschar *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;
+ }
+
+ /* For SMTPS we need to wait for the initial OK response. */
+ if (sx->smtps)
+ {
+ 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;
+ }
+
+ if (sx->esmtp)
+ greeting_cmd = "EHLO";
+ else
+ {
+ greeting_cmd = "HELO";
+ DEBUG(D_transport)
+ debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+ }
+
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "%s %s\r\n",
+ sx->lmtp ? "LHLO" : greeting_cmd, sx->helo_data) < 0)
+ goto SEND_FAILED;
+ good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ '2', sx->ob->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+ sx->helo_response = string_copy(sx->buffer);
+#endif
+ if (!good_response) goto RESPONSE_FAILED;
+ smtp_peer_options = 0;
+ }
+
+/* If the host is required to use a secure channel, ensure that we
+have one. */
+
+else if ( sx->smtps
+# ifdef SUPPORT_DANE
+ || sx->dane
+# endif
+# ifdef EXPERIMENTAL_REQUIRETLS
+ || tls_requiretls & REQUIRETLS_MSG
+# endif
+ || verify_check_given_host(&sx->ob->hosts_require_tls, sx->host) == OK
+ )
+ {
+ errno =
+# ifdef EXPERIMENTAL_REQUIRETLS
+ tls_requiretls & REQUIRETLS_MSG ? ERRNO_REQUIRETLS :
+# endif
+ ERRNO_TLSREQUIRED;
+ message = string_sprintf("a TLS session is required, but %s",
+ smtp_peer_options & OPTION_TLS
+ ? "an attempt to start TLS failed" : "the server did not offer TLS support");
+# if defined(SUPPORT_DANE) && !defined(DISABLE_EVENT)
+ if (sx->dane)
+ (void) event_raise(sx->tblock->event_action, US"dane:fail",
+ smtp_peer_options & OPTION_TLS
+ ? US"validation-failure" /* could do with better detail */
+ : US"starttls-not-supported");
+# endif
+ goto TLS_FAILED;
+ }
+#endif /*SUPPORT_TLS*/
+
+/* If TLS is active, we have just started it up and re-done the EHLO command,
+so its response needs to be analyzed. If TLS is not active and this is a
+continued session down a previously-used socket, we haven't just done EHLO, so
+we skip this. */
+
+if (continue_hostname == NULL
+#ifdef SUPPORT_TLS
+ || tls_out.active.sock >= 0
+#endif
+ )
+ {
+ if (sx->esmtp || sx->lmtp)
+ {
+ sx->peer_offered = ehlo_response(sx->buffer,
+ 0 /* no TLS */
+ | (sx->lmtp && sx->ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+ | OPTION_CHUNKING
+ | OPTION_PRDR
+#ifdef SUPPORT_I18N
+ | (sx->addrlist->prop.utf8_msg ? OPTION_UTF8 : 0)
+ /*XXX if we hand peercaps on to continued-conn processes,
+ must not depend on this addr */
+#endif
+ | OPTION_DSN
+ | OPTION_PIPE
+ | (sx->ob->size_addition >= 0 ? OPTION_SIZE : 0)
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ | (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0)
+#endif
+ );
+
+ /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+ lmtp_ignore_quota option was set. */
+
+ sx->igquotstr = sx->peer_offered & OPTION_IGNQ ? US" IGNOREQUOTA" : US"";
+
+ /* If the response to EHLO specified support for the SIZE parameter, note
+ this, provided size_addition is non-negative. */
+
+ smtp_peer_options |= sx->peer_offered & OPTION_SIZE;
+
+ /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
+ the current host, esmtp will be false, so PIPELINING can never be used. If
+ the current host matches hosts_avoid_pipelining, don't do it. */
+
+ if ( sx->peer_offered & OPTION_PIPE
+ && verify_check_given_host(&sx->ob->hosts_avoid_pipelining, sx->host) != OK)
+ smtp_peer_options |= OPTION_PIPE;
+
+ DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
+ smtp_peer_options & OPTION_PIPE ? "" : "not ");
+
+ if ( sx->peer_offered & OPTION_CHUNKING
+ && verify_check_given_host(&sx->ob->hosts_try_chunking, sx->host) != OK)
+ sx->peer_offered &= ~OPTION_CHUNKING;
+
+ if (sx->peer_offered & OPTION_CHUNKING)
+ {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
+
+#ifndef DISABLE_PRDR
+ if ( sx->peer_offered & OPTION_PRDR
+ && verify_check_given_host(&sx->ob->hosts_try_prdr, sx->host) != OK)
+ sx->peer_offered &= ~OPTION_PRDR;
+
+ if (sx->peer_offered & OPTION_PRDR)
+ {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+#endif
+
+ /* Note if the server supports DSN */
+ smtp_peer_options |= sx->peer_offered & OPTION_DSN;
+ DEBUG(D_transport) debug_printf("%susing DSN\n",
+ sx->peer_offered & OPTION_DSN ? "" : "not ");
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ if (sx->peer_offered & OPTION_REQUIRETLS)
+ {
+ smtp_peer_options |= OPTION_REQUIRETLS;
+ DEBUG(D_transport) debug_printf(
+ tls_requiretls & REQUIRETLS_MSG
+ ? "using REQUIRETLS\n" : "REQUIRETLS offered\n");
+ }
+#endif
+
+ /* Note if the response to EHLO specifies support for the AUTH extension.
+ If it has, check that this host is one we want to authenticate to, and do
+ the business. The host name and address must be available when the
+ authenticator's client driver is running. */
+
+ switch (yield = smtp_auth(sx->buffer, sizeof(sx->buffer), sx->addrlist, sx->host,
+ sx->ob, sx->esmtp, &sx->inblock, &sx->outblock))
+ {
+ default: goto SEND_QUIT;
+ case OK: break;
+ case FAIL_SEND: goto SEND_FAILED;
+ case FAIL: goto RESPONSE_FAILED;
+ }
+ }
+ }
+sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
+
+/* The setting up of the SMTP call is now complete. Any subsequent errors are
+message-specific. */
+
+sx->setting_up = FALSE;
+
+#ifdef SUPPORT_I18N
+if (sx->addrlist->prop.utf8_msg)
+ {
+ uschar * s;
+
+ /* If the transport sets a downconversion mode it overrides any set by ACL
+ for the message. */
+
+ if ((s = sx->ob->utf8_downconvert))
+ {
+ if (!(s = expand_string(s)))
+ {
+ message = string_sprintf("failed to expand utf8_downconvert: %s",
+ expand_string_message);
+ set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ switch (*s)
+ {
+ case '1': sx->addrlist->prop.utf8_downcvt = TRUE;
+ sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+ break;
+ case '0': sx->addrlist->prop.utf8_downcvt = FALSE;
+ sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+ break;
+ case '-': if (s[1] == '1')
+ {
+ sx->addrlist->prop.utf8_downcvt = FALSE;
+ sx->addrlist->prop.utf8_downcvt_maybe = TRUE;
+ }
+ break;
+ }
+ }
+
+ sx->utf8_needed = !sx->addrlist->prop.utf8_downcvt
+ && !sx->addrlist->prop.utf8_downcvt_maybe;
+ DEBUG(D_transport) if (!sx->utf8_needed)
+ debug_printf("utf8: %s downconvert\n",
+ sx->addrlist->prop.utf8_downcvt ? "mandatory" : "optional");
+ }
+
+/* If this is an international message we need the host to speak SMTPUTF8 */
+if (sx->utf8_needed && !(sx->peer_offered & OPTION_UTF8))
+ {
+ errno = ERRNO_UTF8_FWD;
+ goto RESPONSE_FAILED;
+ }
+#endif /*SUPPORT_I18N*/
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ /*XXX should tls_requiretls actually be per-addr? */
+
+if ( tls_requiretls & REQUIRETLS_MSG
+ && !(sx->peer_offered & OPTION_REQUIRETLS)
+ )
+ {
+ sx->setting_up = TRUE;
+ errno = ERRNO_REQUIRETLS;
+ message = US"REQUIRETLS support is required from the server"
+ " but it was not offered";
+ DEBUG(D_transport) debug_printf("%s\n", message);
+ goto TLS_FAILED;
+ }
+#endif
+
+return OK;
+
+
+ {
+ int code;
+
+ RESPONSE_FAILED:
+ message = NULL;
+ sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
+ sx->buffer, &code, &message, &pass_message);
+ yield = DEFER;
+ goto FAILED;
+
+ SEND_FAILED:
+ code = '4';
+ message = US string_sprintf("send() to %s [%s] failed: %s",
+ sx->host->name, sx->host->address, strerror(errno));
+ sx->send_quit = FALSE;
+ yield = DEFER;
+ goto FAILED;
+
+ EHLOHELO_FAILED:
+ code = '4';
+ message = string_sprintf("Remote host closed connection in response to %s"
+ " (EHLO response was: %s)", smtp_command, sx->buffer);
+ sx->send_quit = FALSE;
+ yield = DEFER;
+ goto FAILED;
+
+ /* This label is jumped to directly when a TLS negotiation has failed,
+ or was not done for a host for which it is required. Values will be set
+ in message and errno, and setting_up will always be true. Treat as
+ a temporary error. */
+
+#ifdef SUPPORT_TLS
+ TLS_FAILED:
+# ifdef EXPERIMENTAL_REQUIRETLS
+ if (errno == ERRNO_REQUIRETLS)
+ code = '5', yield = FAIL;
+ /*XXX DSN will be labelled 500; prefer 530 5.7.4 */
+ else
+# endif
+ code = '4', yield = DEFER;
+ goto FAILED;
+#endif
+
+ /* The failure happened while setting up the call; see if the failure was
+ a 5xx response (this will either be on connection, or following HELO - a 5xx
+ after EHLO causes it to try HELO). If so, and there are no more hosts to try,
+ fail all addresses, as this host is never going to accept them. For other
+ errors during setting up (timeouts or whatever), defer all addresses, and
+ yield DEFER, so that the host is not tried again for a while.
+
+ XXX This peeking for another host feels like a layering violation. We want
+ to note the host as unusable, but down here we shouldn't know if this was
+ the last host to try for the addr(list). Perhaps the upper layer should be
+ the one to do set_errno() ? The problem is that currently the addr is where
+ errno etc. are stashed, but until we run out of hosts to try the errors are
+ host-specific. Maybe we should enhance the host_item definition? */
+
+FAILED:
+ sx->ok = FALSE; /* For when reached by GOTO */
+ set_errno(sx->addrlist, errno, message,
+ sx->host->next
+ ? DEFER
+ : code == '5'
+#ifdef SUPPORT_I18N
+ || errno == ERRNO_UTF8_FWD
+#endif
+ ? FAIL : DEFER,
+ pass_message, sx->host
+#ifdef EXPERIMENTAL_DSN_INFO
+ , sx->smtp_greeting, sx->helo_response
+#endif
+ );
+ }
+
+
+SEND_QUIT:
+
+if (sx->send_quit)
+ (void)smtp_write_command(&sx->outblock, SCMD_FLUSH, "QUIT\r\n");
+
+#ifdef SUPPORT_TLS
+if (sx->cctx.tls_ctx)
+ {
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ sx->cctx.tls_ctx = NULL;
+ }
+#endif
+
+/* Close the socket, and return the appropriate value, first setting
+works because the NULL setting is passed back to the calling process, and
+remote_max_parallel is forced to 1 when delivering over an existing connection,
+*/
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
+if (sx->send_quit)
+ {
+ shutdown(sx->cctx.sock, SHUT_WR);
+ if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+ for (rc = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
+ rc--; /* drain socket */
+ sx->send_quit = FALSE;
+ }
+(void)close(sx->cctx.sock);
+sx->cctx.sock = -1;
+
+#ifndef DISABLE_EVENT
+(void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
+#endif
+
+continue_transport = NULL;
+continue_hostname = NULL;
+return yield;
+}
+
+
+
+
+/* Create the string of options that will be appended to the MAIL FROM:
+in the connection context buffer */
+
+static int
+build_mailcmd_options(smtp_context * sx, address_item * addrlist)
+{
+uschar * p = sx->buffer;
+address_item * addr;
+int address_count;
+
+*p = 0;
+
+/* If we know the receiving MTA supports the SIZE qualification, and we know it,
+send it, adding something to the message size to allow for imprecision
+and things that get added en route. Exim keeps the number of lines
+in a message, so we can give an accurate value for the original message, but we
+need some additional to handle added headers. (Double "." characters don't get
+included in the count.) */
+
+if ( message_size > 0
+ && sx->peer_offered & OPTION_SIZE && !(sx->avoid_option & OPTION_SIZE))
+ {
+/*XXX problem here under spool_files_wireformat?
+Or just forget about lines? Or inflate by a fixed proportion? */
+
+ sprintf(CS p, " SIZE=%d", message_size+message_linecount+sx->ob->size_addition);
+ while (*p) p++;
+ }
+
+#ifndef DISABLE_PRDR
+/* If it supports Per-Recipient Data Reponses, and we have omre than one recipient,
+request that */
+
+sx->prdr_active = FALSE;
+if (sx->peer_offered & OPTION_PRDR)
+ for (addr = addrlist; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ {
+ for (addr = addr->next; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ { /* at least two recipients to send */
+ sx->prdr_active = TRUE;
+ sprintf(CS p, " PRDR"); p += 5;
+ break;
+ }
+ break;
+ }
+#endif
+
+#ifdef SUPPORT_I18N
+/* If it supports internationalised messages, and this meesage need that,
+request it */
+
+if ( sx->peer_offered & OPTION_UTF8
+ && addrlist->prop.utf8_msg
+ && !addrlist->prop.utf8_downcvt
+ )
+ Ustrcpy(p, " SMTPUTF8"), p += 9;
+#endif
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+ Ustrcpy(p, " REQUIRETLS") , p += 11;
+#endif
+
+/* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
+for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
+ addr && address_count < sx->max_rcpt;
+ addr = addr->next) if (addr->transport_return == PENDING_DEFER)
+ {
+ address_count++;
+ if (!(addr->dsn_flags & rf_dsnlasthop))
+ {
+ sx->dsn_all_lasthop = FALSE;
+ break;
+ }
+ }
+
+/* Add any DSN flags to the mail command */
+
+if (sx->peer_offered & OPTION_DSN && !sx->dsn_all_lasthop)
+ {
+ if (dsn_ret == dsn_ret_hdrs)
+ { Ustrcpy(p, " RET=HDRS"); p += 9; }
+ else if (dsn_ret == dsn_ret_full)
+ { Ustrcpy(p, " RET=FULL"); p += 9; }
+
+ if (dsn_envid)
+ {
+ string_format(p, sizeof(sx->buffer) - (p-sx->buffer), " ENVID=%s", dsn_envid);
+ while (*p) p++;
+ }
+ }
+
+/* If an authenticated_sender override has been specified for this transport
+instance, expand it. If the expansion is forced to fail, and there was already
+an authenticated_sender for this message, the original value will be used.
+Other expansion failures are serious. An empty result is ignored, but there is
+otherwise no check - this feature is expected to be used with LMTP and other
+cases where non-standard addresses (e.g. without domains) might be required. */
+
+if (smtp_mail_auth_str(p, sizeof(sx->buffer) - (p-sx->buffer), addrlist, sx->ob))
+ return ERROR;
+
+return OK;
+}
+
+
+static void
+build_rcptcmd_options(smtp_context * sx, const address_item * addr)
+{
+uschar * p = sx->buffer;
+*p = 0;