Enables concurrent use from a single process, and thereby use for cutthrough delivery.
As a side-effect EHLO and TLS use for verify callouts introduced.
This was a manual import from elsewhere and is known to fail the test-suite.
int string_datestamp_type = -1;
BOOL timestamps_utc = FALSE;
-BOOL tls_certificate_verified = FALSE;
-uschar *tls_cipher = NULL;
-uschar *tls_peerdn = NULL;
-#ifdef SUPPORT_TLS
-uschar *tls_sni = NULL;
-#endif
+tls_support tls_in = {
+ -1, /* tls_active */
+ FALSE, /* tls_certificate_verified */
+ NULL, /* tls_cipher */
+ FALSE, /* tls_on_connect */
+ NULL, /* tls_on_connect_ports */
+ NULL, /* tls_peerdn */
+ NULL /* tls_sni */
+};
tree_node *tree_duplicates = NULL;
tree_node *tree_nonrecipients = NULL;
test whether it was successful or not. (This is for optional verification; for
mandatory verification, the connection doesn't last this long.) */
- if (tls_certificate_verified) return OK;
+ if (tls_in.certificate_verified) return OK;
*user_msgptr = US"no verified certificate";
return FAIL;
writing is poorly documented. */
case ACLC_ENCRYPTED:
- if (tls_cipher == NULL) rc = FAIL; else
+ if (tls_in.cipher == NULL) rc = FAIL; else
{
uschar *endcipher = NULL;
- uschar *cipher = Ustrchr(tls_cipher, ':');
- if (cipher == NULL) cipher = tls_cipher; else
+ uschar *cipher = Ustrchr(tls_in.cipher, ':');
+ if (cipher == NULL) cipher = tls_in.cipher; else
{
endcipher = Ustrchr(++cipher, ':');
if (endcipher != NULL) *endcipher = 0;
rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
-/*XXX cutthrough - if requested,
-and WHERE_RCPT and not yet opened conn as reult of verify,
-and rc==OK
+/* Cutthrough - if requested,
+and WHERE_RCPT and not yet opened conn as result of recipient-verify,
+and rcpt acl returned accept,
+and first recipient (cancel on any subsequents)
open one now and run it up to RCPT acceptance.
-Query: what to do with xple rcpts? Avoid for now by only doing on 1st, and
-cancelling on any subsequents.
A failed verify should cancel cutthrough request.
-For now, ensure we only accept requests to cutthrough pre-data. Maybe relax that later.
-On a pre-data acl, if not accept and a cutthrough conn is open, close it. If accept and
-a cutthrough conn is open, send DATA command and setup byte-by-byte copy mode and
-cancel spoolfile-write mode.
-NB this means no DATA acl, no content checking - might want an option for that?.
-
-Initial implementation: dual-write to spool (do the no-spool later).
+Initial implementation: dual-write to spool.
Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection.
Cease cutthrough copy on rxd final dot; do not send one.
perm-rejected, reflect that to the original sender - and dump the spooled copy.
If temp-reject, close the conn (and keep the spooled copy).
If conn-failure, no action (and keep the spooled copy).
-
-
-XXX What about TLS? Callouts never seem to do it atm. but we ought to support it eventually.
-XXX What about pipelining? Callouts don't, and we probably don't care too much.
*/
switch (where)
{
return DEFER;
}
-if (tls_cipher)
+if (tls_in.cipher)
{
- rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_bits);
+ rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
if (rc != SASL_OK)
{
HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
- tls_bits, sasl_errstring(rc, NULL, NULL));
+ tls_in.bits, sasl_errstring(rc, NULL, NULL));
auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
sasl_done();
return DEFER;
}
else
- HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_bits);
+ HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
}
else
HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
/* Added by PH: extra fields when TLS is in use or if the TCP/IP
connection is local. */
- if (tls_cipher != NULL)
+ if (tls_in.cipher != NULL)
auth_extra_data = string_sprintf("secured\t%s%s",
- tls_certificate_verified? "valid-client-cert" : "",
- tls_certificate_verified? "\t" : "");
+ tls_in.certificate_verified? "valid-client-cert" : "",
+ tls_in.certificate_verified? "\t" : "");
else if (interface_address != NULL &&
Ustrcmp(sender_host_address, interface_address) == 0)
auth_extra_data = US"secured\t";
/* Check for a tls-on-connect port */
- if (host_is_tls_on_connect_port(interface_port)) tls_on_connect = TRUE;
+ if (host_is_tls_on_connect_port(interface_port)) tls_in.on_connect = TRUE;
/* Expand smtp_active_hostname if required. We do not do this any earlier,
because it may depend on the local interface address (indeed, that is most
the data structures if necessary. */
#ifdef SUPPORT_TLS
- tls_close(FALSE);
+ tls_close(FALSE, FALSE);
#endif
/* Reset SIGHUP and SIGCHLD in the child in both cases. */
+/* If msg is NULL this is a delivery log and logchar is used. Otherwise
+this is a nonstandard call; no two-characher delivery flag is written
+but sender-host and sender are prefixed and "msg" is inserted in the log line.
+
+Arguments:
+ flags passed to log_write()
+*/
void
-delivery_log(address_item * addr, int logchar)
+delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
{
uschar *log_address;
int size = 256; /* Used for a temporary, */
pointer to a single host item in their host list, for use by the transport. */
s = reset_point = store_get(size);
-s[ptr++] = logchar;
log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
-s = string_append(s, &size, &ptr, 2, US"> ", log_address);
+if (msg)
+ s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
+else
+ {
+ s[ptr++] = logchar;
+ s = string_append(s, &size, &ptr, 2, US"> ", log_address);
+ }
-if ((log_extra_selector & LX_sender_on_delivery) != 0)
+if ((log_extra_selector & LX_sender_on_delivery) != 0 || msg)
s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
#ifdef EXPERIMENTAL_SRS
(log_extra_selector & LX_return_path_on_delivery) != 0)
s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
-/* For a delivery from a system filter, there may not be a router */
+if (msg)
+ s = string_append(s, &size, &ptr, 2, US" ", msg);
+/* For a delivery from a system filter, there may not be a router */
if (addr->router != NULL)
s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
store we used to build the line after writing it. */
s[ptr] = 0;
-log_write(0, LOG_MAIN, "%s", s);
+log_write(0, flags, "%s", s);
store_reset(reset_point);
return;
}
child_done(addr, now);
}
- delivery_log(addr, logchar);
+ delivery_log(LOG_MAIN, addr, logchar, NULL);
}
/* The certificate verification status goes into the flags */
- if (tls_certificate_verified) setflag(addr, af_cert_verified);
+ if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
/* Use an X item only if there's something to send */
if (smtp_input)
{
#ifdef SUPPORT_TLS
- tls_close(FALSE); /* Shut down the TLS library */
+ tls_close(FALSE, FALSE); /* Shut down the TLS library */
#endif
(void)close(fileno(smtp_in));
(void)close(fileno(smtp_out));
/* -tls-on-connect: don't wait for STARTTLS (for old clients) */
#ifdef SUPPORT_TLS
- else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_on_connect = TRUE;
+ else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_in.on_connect = TRUE;
#endif
else badarg = TRUE;
interface_address = host_ntoa(-1, &interface_sock, NULL,
&interface_port);
- if (host_is_tls_on_connect_port(interface_port)) tls_on_connect = TRUE;
+ if (host_is_tls_on_connect_port(interface_port)) tls_in.on_connect = TRUE;
if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
{
{ "srs_status", vtype_stringptr, &srs_status },
#endif
{ "thisaddress", vtype_stringptr, &filter_thisaddress },
- { "tls_bits", vtype_int, &tls_bits },
- { "tls_certificate_verified", vtype_int, &tls_certificate_verified },
- { "tls_cipher", vtype_stringptr, &tls_cipher },
- { "tls_peerdn", vtype_stringptr, &tls_peerdn },
-#ifdef SUPPORT_TLS
- { "tls_sni", vtype_stringptr, &tls_sni },
+
+ { "tls_bits", vtype_int, &tls_in.bits },
+ { "tls_certificate_verified", vtype_int, &tls_in.certificate_verified },
+ { "tls_cipher", vtype_stringptr, &tls_in.cipher },
+ { "tls_peerdn", vtype_stringptr, &tls_in.peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_sni", vtype_stringptr, &tls_in.sni },
#endif
+ { "tls_out_bits", vtype_int, &tls_out.bits },
+ { "tls_out_certificate_verified", vtype_int, &tls_out.certificate_verified },
+ { "tls_out_cipher", vtype_stringptr, &tls_out.cipher },
+ { "tls_out_peerdn", vtype_stringptr, &tls_out.peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_out_sni", vtype_stringptr, &tls_out.sni },
+#endif
+
{ "tod_bsdinbox", vtype_todbsdin, NULL },
{ "tod_epoch", vtype_tode, NULL },
{ "tod_epoch_l", vtype_todel, NULL },
extern int tls_client_start(int, host_item *, address_item *, uschar *,
uschar *, uschar *, uschar *, uschar *, uschar *, uschar *,
int, int);
-extern void tls_close(BOOL);
+extern void tls_close(BOOL, BOOL);
extern int tls_feof(void);
extern int tls_ferror(void);
extern int tls_getc(void);
-extern int tls_read(uschar *, size_t);
+extern int tls_read(BOOL, uschar *, size_t);
extern int tls_server_start(const uschar *);
extern BOOL tls_smtp_buffered(void);
extern int tls_ungetc(int);
-extern int tls_write(const uschar *, size_t);
+extern int tls_write(BOOL, int, const uschar *, size_t);
extern uschar *tls_validate_require_cipher(void);
extern void tls_version_report(FILE *);
#ifndef USE_GNUTLS
extern void decode_bits(unsigned int *, unsigned int *,
int, int, uschar *, bit_table *, int, uschar *, int);
extern address_item *deliver_make_addr(uschar *, BOOL);
-extern void delivery_log(address_item *, int);
+extern void delivery_log(int, address_item *, int, uschar *);
extern int deliver_message(uschar *, BOOL, BOOL);
extern void deliver_msglog(const char *, ...) PRINTF_FUNCTION(1,2);
extern void deliver_set_expansions(address_item *);
cluttered in several places (e.g. during logging) if we can always refer to
them. Also, the tls_ variables are now always visible. */
-BOOL tls_active = -1;
-int tls_bits = 0;
-BOOL tls_certificate_verified = FALSE;
-uschar *tls_cipher = NULL;
-BOOL tls_on_connect = FALSE;
-uschar *tls_on_connect_ports = NULL;
-uschar *tls_peerdn = NULL;
+tls_support tls_in = {
+ -1, /* tls_active */
+ 0, /* tls_bits */
+ FALSE,/* tls_certificate_verified */
+ NULL, /* tls_cipher */
+ FALSE,/* tls_on_connect */
+ NULL, /* tls_on_connect_ports */
+ NULL, /* tls_peerdn */
+ NULL /* tls_sni */
+};
+tls_support tls_out = {
+ -1, /* tls_active */
+ 0, /* tls_bits */
+ FALSE,/* tls_certificate_verified */
+ NULL, /* tls_cipher */
+ FALSE,/* tls_on_connect */
+ NULL, /* tls_on_connect_ports */
+ NULL, /* tls_peerdn */
+ NULL /* tls_sni */
+};
+
#ifdef SUPPORT_TLS
BOOL gnutls_compat_mode = FALSE;
uschar *tls_privatekey = NULL;
BOOL tls_remember_esmtp = FALSE;
uschar *tls_require_ciphers = NULL;
-uschar *tls_sni = NULL;
uschar *tls_try_verify_hosts = NULL;
uschar *tls_verify_certificates= NULL;
uschar *tls_verify_hosts = NULL;
cluttered in several places (e.g. during logging) if we can always refer to
them. Also, the tls_ variables are now always visible. */
-extern int tls_active; /* fd/socket when in a TLS session */
-extern int tls_bits; /* bits used in TLS session */
-extern BOOL tls_certificate_verified; /* Client certificate verified */
-extern uschar *tls_cipher; /* Cipher used */
-extern BOOL tls_on_connect; /* For older MTAs that don't STARTTLS */
-extern uschar *tls_on_connect_ports; /* Ports always tls-on-connect */
-extern uschar *tls_peerdn; /* DN from peer */
+typedef struct {
+ int active; /* fd/socket when in a TLS session */
+ int bits; /* bits used in TLS session */
+ BOOL certificate_verified; /* Client certificate verified */
+ uschar *cipher; /* Cipher used */
+ BOOL on_connect; /* For older MTAs that don't STARTTLS */
+ uschar *on_connect_ports; /* Ports always tls-on-connect */
+ uschar *peerdn; /* DN from peer */
+#ifndef USE_GNUTLS
+ uschar *sni; /* Server Name Indication */
+#endif
+} tls_support;
+extern tls_support tls_in;
+extern tls_support tls_out;
#ifdef SUPPORT_TLS
extern BOOL gnutls_compat_mode; /* Less security, more compatibility */
extern uschar *tls_privatekey; /* Private key file */
extern BOOL tls_remember_esmtp; /* For YAEB */
extern uschar *tls_require_ciphers; /* So some can be avoided */
-extern uschar *tls_sni; /* Server Name Indication */
extern uschar *tls_try_verify_hosts; /* Optional client verification */
extern uschar *tls_verify_certificates;/* Path for certificates to check */
extern uschar *tls_verify_hosts; /* Mandatory client verification */
{
int sep = 0;
uschar buffer[32];
-uschar *list = tls_on_connect_ports;
+uschar *list = tls_in.on_connect_ports;
uschar *s;
-if (tls_on_connect) return TRUE;
+if (tls_in.on_connect) return TRUE;
while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
{
close down of the connection), set errno to zero; otherwise leave it alone. */
#ifdef SUPPORT_TLS
-if (tls_active == sock)
- rc = tls_read(buffer, buffsize);
+if (tls_out.active == sock)
+ rc = tls_read(FALSE, buffer, buffsize);
+else if (tls_in.active == sock)
+ rc = tls_read(TRUE, buffer, buffsize);
else
#endif
rc = recv(sock, buffer, buffsize, 0);
a no-op once an SSL session is in progress. */
#ifdef SUPPORT_TLS
-#define mac_smtp_fflush() if (tls_active < 0) fflush(smtp_out);
+#define mac_smtp_fflush() if (tls_in.active < 0) fflush(smtp_out);
#else
#define mac_smtp_fflush() fflush(smtp_out);
#endif
{ "tls_crl", opt_stringptr, &tls_crl },
{ "tls_dh_max_bits", opt_int, &tls_dh_max_bits },
{ "tls_dhparam", opt_stringptr, &tls_dhparam },
-#if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
+# if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
{ "tls_ocsp_file", opt_stringptr, &tls_ocsp_file },
-#endif
- { "tls_on_connect_ports", opt_stringptr, &tls_on_connect_ports },
+# endif
+ { "tls_on_connect_ports", opt_stringptr, &tls_in.on_connect_ports },
{ "tls_privatekey", opt_stringptr, &tls_privatekey },
{ "tls_remember_esmtp", opt_bool, &tls_remember_esmtp },
{ "tls_require_ciphers", opt_stringptr, &tls_require_ciphers },
Returns: One of the END_xxx values indicating why it stopped reading
*/
-/*XXX cutthrough - need to copy to destination, not including the
- terminating dot, canonicalizing newlines.
-*/
static int
read_message_data_smtp(FILE *fout)
uschar *resent_prefix = US"";
uschar *blackholed_by = NULL;
uschar *blackhole_log_msg = US"";
+int cutthrough_done;
flock_t lock_data;
error_block *bad_addresses = NULL;
/* Extracting the recipient list from an input file is incompatible with
cutthrough delivery with the no-spool option. It shouldn't be possible
- to set up the combination, but just in case kill any ongoing connection. */
-/*XXX add no-spool */
+to set up the combination, but just in case kill any ongoing connection. */
if (extract_recip || !smtp_input)
cancel_cutthrough_connection();
case htype_received:
h->type = htype_received;
-/*XXX cutthrough delivery - need to error on excessive number here */
received_count++;
break;
return message_ended == END_DOT;
}
-/*XXX cutthrough deliver:
+/* Cutthrough delivery:
We have to create the Received header now rather than at the end of reception,
so the timestamp behaviour is a change to the normal case.
XXX Ensure this gets documented XXX.
+ Having created it, send the headers to the destination.
*/
if (cutthrough_fd >= 0)
{
+ if (received_count > received_headers_max)
+ {
+ cancel_cutthrough_connection();
+ if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
+ log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+ "Too many \"Received\" headers",
+ sender_address,
+ (sender_fullhost == NULL)? "" : " H=",
+ (sender_fullhost == NULL)? US"" : sender_fullhost,
+ (sender_ident == NULL)? "" : " U=",
+ (sender_ident == NULL)? US"" : sender_ident);
+ message_id[0] = 0; /* Indicate no message accepted */
+ smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
+ goto TIDYUP; /* Skip to end of function */
+ }
received_header_gen();
add_acl_headers(US"MAIL or RCPT");
(void) cutthrough_headers_send();
}
-
-
-/*XXX cutthrough deliver:
- Here's where we open the data spoolfile. Want to optionally avoid.
-*/
+
/* Open a new spool file for the data portion of the message. We need
to access it both via a file descriptor and a stream. Try to make the
{
uschar *s = next->text;
int len = next->slen;
- /*XXX cutthrough - writing the data spool file here. Want to optionally avoid. */
(void)fwrite(s, 1, len, data_file);
body_linecount++; /* Assumes only 1 line */
}
(indicated by '.'), or might have encountered an error while writing the
message id or "next" line. */
-/* XXX cutthrough - no-spool option....... */
if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
{
if (smtp_input)
{
- /*XXX cutthrough - writing the data spool file here. Want to optionally avoid. */
- /* Would suffice to leave data_file arg NULL */
message_ended = read_message_data_smtp(data_file);
receive_linecount++; /* The terminating "." line */
}
the input in cases of output errors, since the far end doesn't expect to see
anything until the terminating dot line is sent. */
-/* XXX cutthrough - no-spool option....... */
if (fflush(data_file) == EOF || ferror(data_file) ||
EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
{
/* No I/O errors were encountered while writing the data file. */
-/*XXX cutthrough - avoid message if no-spool option */
DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
exit. (This can't be SMTP, which always ensures there's at least one
syntactically good recipient address.) */
-/*XXX cutthrough - can't if no-spool option. extract_recip is a fn arg.
- Make incompat with no-spool at fn start. */
-
if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
{
DEBUG(D_receive)
add_acl_headers(US"MAIL or RCPT");
}
-else if (data_fd >= 0)
+else
message_body_size = (fstat(data_fd, &statbuf) == 0)?
statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
-else
- /*XXX cutthrough - XXX how to get the body size? */
- /* perhaps a header-size to subtract from message_size? */
- message_body_size = message_size - 1;
/* If an ACL is specified for checking things at this stage of reception of a
message, run it, unless all the recipients were removed by "discard" in earlier
#ifndef DISABLE_DKIM
if (!dkim_disable_verify)
{
-/* XXX cutthrough - no-spool option....... */
/* Finish verification, this will log individual signature results to
the mainlog */
dkim_exim_verify_finish();
#endif /* DISABLE_DKIM */
#ifdef WITH_CONTENT_SCAN
-/* XXX cutthrough - no-spool option....... */
if (recipients_count > 0 &&
acl_smtp_mime != NULL &&
!run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
/* Check the recipients count again, as the MIME ACL might have changed
them. */
-/* XXX cutthrough - no-spool option must document that data-acl has no file access */
-/* but can peek at headers */
-
if (acl_smtp_data != NULL && recipients_count > 0)
{
rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
if (local_scan_timeout > 0) alarm(local_scan_timeout);
-/* XXX cutthrough - no-spool option..... */
rc = local_scan(data_fd, &local_scan_data);
alarm(0);
os_non_restarting_signal(SIGALRM, sigalrm_handler);
#ifdef EXPERIMENTAL_BRIGHTMAIL
if (bmi_run == 1) {
/* rewind data file */
- /* XXX cutthrough - no-spool option..... */
lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
bmi_verdicts = bmi_process_message(header_list, data_fd);
};
else
{
- /*XXX cutthrough -
- Optionally want to avoid writing spool files (when no data-time filtering needed) */
-
if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
{
log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
s = add_host_info_for_log(s, &size, &sptr);
#ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
- s = string_append(s, &size, &sptr, 2, US" X=", tls_cipher);
+if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+ s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
- tls_cipher != NULL)
+ tls_in.cipher != NULL)
s = string_append(s, &size, &sptr, 2, US" CV=",
- tls_certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
+ tls_in.certificate_verified? "yes":"no");
+if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
s = string_append(s, &size, &sptr, 3, US" DN=\"",
- string_printing(tls_peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+ string_printing(tls_in.peerdn), US"\"");
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
s = string_append(s, &size, &sptr, 3, US" SNI=\"",
- string_printing(tls_sni), US"\"");
+ string_printing(tls_in.sni), US"\"");
#endif
if (sender_host_authenticated != NULL)
/* The connection has not gone away; we really are going to take responsibility
for this message. */
-/*XXX cutthrough - had sender last-dot; assume we've sent or bufferred all
+/* Cutthrough - had sender last-dot; assume we've sent (or bufferred) all
data onward by now.
- Send dot onward. If accepted, can the spooled files, log as delivered and accept
+ Send dot onward. If accepted, wipe the spooled files, log as delivered and accept
the sender's dot (below).
- If not accepted: copy response to sender, can the spooled files, log approriately.
+ If rejected: copy response to sender, wipe the spooled files, log approriately.
+ If temp-reject: accept to sender, keep the spooled files.
Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
+
+ XXX We do not handle queue-only, freezing, or blackholes.
*/
if(cutthrough_fd >= 0)
{
uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */
+ /* Logging was done in finaldot() */
switch(msg[0])
- {
- case '2': /* Accept. Do the same to the source; dump any spoolfiles. */
- /* logging was done in finaldot() */
- if(data_file != NULL)
- {
- sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- }
- break;
-
- default: /* Unknown response, or error. Treat as temp-reject. */
- case '4': /* Temp-reject. If we wrote spoolfiles, keep them and accept. */
- /* If not, temp-reject the source. */
- /*XXX could we mark the spoolfile queue-only or already-tried? */
- log_write(0, LOG_MAIN, "cutthrough target temp-reject: %s", msg);
- if(data_file == NULL)
- smtp_reply= msg; /* Pass on the exact error */
- break;
-
- case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */
- log_write(0, LOG_MAIN, "cutthrough target perm-reject: %s", msg);
- smtp_reply= msg; /* Pass on the exact error */
- if(data_file != NULL)
- {
- sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- }
- break;
- }
+ {
+ case '2': /* Accept. Do the same to the source; dump any spoolfiles. */
+ cutthrough_done = 3;
+ break; /* message_id needed for SMTP accept below */
+
+ default: /* Unknown response, or error. Treat as temp-reject. */
+ case '4': /* Temp-reject. Keep spoolfiles and accept. */
+ cutthrough_done = 1; /* Avoid the usual immediate delivery attempt */
+ break; /* message_id needed for SMTP accept below */
+
+ case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */
+ smtp_reply= msg; /* Pass on the exact error */
+ cutthrough_done = 2;
+ break;
+ }
}
+else
+ cutthrough_done = 0;
if(smtp_reply == NULL)
{
if (!smtp_batched_input)
{
-/*XXX cutthrough - here's where the originating sender gets given the data-acceptance */
if (smtp_reply == NULL)
{
if (fake_response != OK)
smtp_printf("%.1024s\r\n", smtp_reply);
}
- if (cutthrough_delivery)
- {
- log_write(0, LOG_MAIN, "Completed");
- message_id[0] = 0; /* Prevent a delivery from starting */
+ switch (cutthrough_done)
+ {
+ case 3: log_write(0, LOG_MAIN, "Completed"); /* Delivery was done */
+ case 2: { /* Delete spool files */
+ sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
+ message_subdir, message_id);
+ Uunlink(spool_name);
+ sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
+ message_subdir, message_id);
+ Uunlink(spool_name);
+ sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
+ message_subdir, message_id);
+ Uunlink(spool_name);
+ }
+ case 1: message_id[0] = 0; /* Prevent a delivery from starting */
+ default:break;
}
+ cutthrough_delivery = FALSE;
}
/* For batched SMTP, generate an error message on failure, and do
/* Now write the string */
#ifdef SUPPORT_TLS
-if (tls_active >= 0)
+if (tls_in.active >= 0)
{
- if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1;
+ if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+ smtp_write_error = -1;
}
else
#endif
int
smtp_fflush(void)
{
-if (tls_active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
return smtp_write_error;
}
sig = sig; /* Keep picky compilers happy */
log_write(L_lost_incoming_connection,
LOG_MAIN, "SMTP command timeout on%s connection from %s",
- (tls_active >= 0)? " TLS" : "",
+ (tls_in.active >= 0)? " TLS" : "",
host_and_ident(FALSE));
if (smtp_batched_input)
moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
struct timeval tzero;
if (!smtp_enforce_sync || sender_host_address == NULL ||
- sender_host_notsocket || tls_active >= 0)
+ sender_host_notsocket || tls_in.active >= 0)
return TRUE;
fd = fileno(smtp_in);
}
#ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
- s = string_append(s, &size, &ptr, 2, US" X=", tls_cipher);
+if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+ s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
- tls_cipher != NULL)
+ tls_in.cipher != NULL)
s = string_append(s, &size, &ptr, 2, US" CV=",
- tls_certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
+ tls_in.certificate_verified? "yes":"no");
+if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
s = string_append(s, &size, &ptr, 3, US" DN=\"",
- string_printing(tls_peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+ string_printing(tls_in.peerdn), US"\"");
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
s = string_append(s, &size, &ptr, 3, US" SNI=\"",
- string_printing(tls_sni), US"\"");
+ string_printing(tls_in.sni), US"\"");
#endif
sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
authenticated_by = NULL;
#ifdef SUPPORT_TLS
-tls_cipher = tls_peerdn = NULL;
+tls_in.cipher = tls_in.peerdn = NULL;
tls_advertised = FALSE;
#endif
smtps port for use with older style SSL MTAs. */
#ifdef SUPPORT_TLS
- if (tls_on_connect &&
- tls_server_start(tls_require_ciphers) != OK)
+ if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
return FALSE;
#endif
sender_host_authenticated = au->name;
authentication_failed = FALSE;
received_protocol =
- protocols[pextend + pauthed + ((tls_active >= 0)? pcrpted:0)] +
+ protocols[pextend + pauthed + ((tls_in.active >= 0)? pcrpted:0)] +
((sender_host_address != NULL)? pnlocal : 0);
s = ss = US"235 Authentication succeeded";
authenticated_by = au;
host_build_sender_fullhost(); /* Rebuild */
set_process_info("handling%s incoming connection from %s",
- (tls_active >= 0)? " TLS" : "", host_and_ident(FALSE));
+ (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));
/* Verify if configured. This doesn't give much security, but it does
make some people happy to be able to do it. If helo_required is set,
secure connection. */
#ifdef SUPPORT_TLS
- if (tls_active < 0 &&
+ if (tls_in.active < 0 &&
verify_check_host(&tls_advertise_hosts) != FAIL)
{
s = string_cat(s, &size, &ptr, smtp_code, 3);
s[ptr] = 0;
#ifdef SUPPORT_TLS
- if (tls_active >= 0) (void)tls_write(s, ptr); else
+ if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
#endif
(void)fwrite(s, 1, ptr, smtp_out);
received_protocol = (esmtp?
protocols[pextend +
((sender_host_authenticated != NULL)? pauthed : 0) +
- ((tls_active >= 0)? pcrpted : 0)]
+ ((tls_in.active >= 0)? pcrpted : 0)]
:
- protocols[pnormal + ((tls_active >= 0)? pcrpted : 0)])
+ protocols[pnormal + ((tls_in.active >= 0)? pcrpted : 0)])
+
((sender_host_address != NULL)? pnlocal : 0);
{
DEBUG(D_any)
debug_printf("Non-empty input buffer after STARTTLS; naive attack?");
- if (tls_active < 0)
+ if (tls_in.active < 0)
smtp_inend = smtp_inptr = smtp_inbuffer;
/* and if TLS is already active, tls_server_start() should fail */
}
}
/* Hard failure. Reject everything except QUIT or closed connection. One
- cause for failure is a nested STARTTLS, in which case tls_active remains
+ cause for failure is a nested STARTTLS, in which case tls_in.active remains
set, but we must still reject all incoming commands. */
DEBUG(D_tls) debug_printf("TLS failed to start\n");
break;
}
}
- tls_close(TRUE);
+ tls_close(TRUE, TRUE);
break;
#endif
smtp_respond(US"221", 3, TRUE, user_msg);
#ifdef SUPPORT_TLS
- tls_close(TRUE);
+ tls_close(TRUE, TRUE);
#endif
done = 2;
buffer[0] = 0;
Ustrcat(buffer, " AUTH");
#ifdef SUPPORT_TLS
- if (tls_active < 0 &&
+ if (tls_in.active < 0 &&
verify_check_host(&tls_advertise_hosts) != FAIL)
Ustrcat(buffer, " STARTTLS");
#endif
int rc;
#ifdef SUPPORT_TLS
-if (tls_active == outblock->sock)
- rc = tls_write(outblock->buffer, outblock->ptr - outblock->buffer);
+if (tls_out.active == outblock->sock)
+ rc = tls_write(FALSE, outblock->buffer, outblock->ptr - outblock->buffer);
else
#endif
#endif
#ifdef SUPPORT_TLS
-tls_certificate_verified = FALSE;
-tls_cipher = NULL;
-tls_peerdn = NULL;
-tls_sni = NULL;
+tls_in.certificate_verified = FALSE;
+tls_in.cipher = NULL;
+tls_in.peerdn = NULL;
+tls_in.sni = NULL;
#endif
#ifdef WITH_CONTENT_SCAN
#ifdef SUPPORT_TLS
case 't':
if (Ustrncmp(p, "ls_certificate_verified", 23) == 0)
- tls_certificate_verified = TRUE;
+ tls_in.certificate_verified = TRUE;
else if (Ustrncmp(p, "ls_cipher", 9) == 0)
- tls_cipher = string_copy(big_buffer + 12);
+ tls_in.cipher = string_copy(big_buffer + 12);
else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
- tls_peerdn = string_unprinting(string_copy(big_buffer + 12));
+ tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
else if (Ustrncmp(p, "ls_sni", 6) == 0)
- tls_sni = string_unprinting(string_copy(big_buffer + 9));
+ tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
break;
#endif
#endif
#ifdef SUPPORT_TLS
-if (tls_certificate_verified) fprintf(f, "-tls_certificate_verified\n");
-if (tls_cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_cipher);
-if (tls_peerdn != NULL) fprintf(f, "-tls_peerdn %s\n", string_printing(tls_peerdn));
-if (tls_sni != NULL) fprintf(f, "-tls_sni %s\n", string_printing(tls_sni));
+if (tls_in.certificate_verified) fprintf(f, "-tls_certificate_verified\n");
+if (tls_in.cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_in.cipher);
+if (tls_in.peerdn != NULL) fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
+if (tls_in.sni != NULL) fprintf(f, "-tls_sni %s\n", string_printing(tls_in.sni));
#endif
/* To complete the envelope, write out the tree of non-recipients, followed by
be set to point to content in one of these instances, as appropriate for
the stage of the process lifetime.
-Not handled here: globals tls_active, tls_bits, tls_cipher, tls_peerdn,
-tls_certificate_verified, tls_channelbinding_b64, tls_sni.
+Not handled here: global tls_channelbinding_b64. /*XXX JGH */
*/
typedef struct exim_gnutls_state {
uschar *exp_tls_crl;
uschar *exp_tls_require_ciphers;
+ tls_support *tlsp;
+
uschar *xfer_buffer;
int xfer_buffer_lwm;
int xfer_buffer_hwm;
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
NULL, 0, 0, 0, 0,
};
second connection. */
static exim_gnutls_state_st state_server, state_client;
-static exim_gnutls_state_st *current_global_tls_state;
/* dh_params are initialised once within the lifetime of a process using TLS;
if we used TLS in a long-lived daemon, we'd have to reconsider this. But we
tls_cipher a string
tls_peerdn a string
tls_sni a (UTF-8) string
-Also:
- current_global_tls_state for API limitations
Argument:
state the relevant exim_gnutls_state_st *
*/
static void
-extract_exim_vars_from_tls_state(exim_gnutls_state_st *state)
+extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server)
{
gnutls_cipher_algorithm_t cipher;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
gnutls_datum_t channel;
#endif
-current_global_tls_state = state;
-
-tls_active = state->fd_out;
+state->tlsp->active = state->fd_out;
cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
-tls_bits = gnutls_cipher_get_key_size(cipher) * 8;
+state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
-tls_cipher = state->ciphersuite;
+state->tlsp->cipher = state->ciphersuite;
-DEBUG(D_tls) debug_printf("cipher: %s\n", tls_cipher);
+DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
-tls_certificate_verified = state->peer_cert_verified;
+state->tlsp->certificate_verified = state->peer_cert_verified;
/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
}
#endif
-tls_peerdn = state->peerdn;
-
-tls_sni = state->received_sni;
+state->tlsp->peerdn = state->peerdn;
+state->tlsp->sni = state->received_sni;
}
cas CA certs file
crl CRL file
require_ciphers tls_require_ciphers setting
+ caller_state returned state-info structure
Returns: OK/DEFER/FAIL
*/
{
state = &state_client;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+ state->tlsp = &tls_out;
DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
rc = gnutls_init(&state->session, GNUTLS_CLIENT);
}
{
state = &state_server;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+ state->tlsp = &tls_in;
DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
rc = gnutls_init(&state->session, GNUTLS_SERVER);
}
/* set SNI in client, only */
if (host)
{
- if (!expand_check_tlsvar(tls_sni))
+ if (!expand_check_tlsvar(state->tlsp->sni))
return DEFER;
if (state->exp_tls_sni && *state->exp_tls_sni)
{
}
*caller_state = state;
-/* needs to happen before callbacks during handshake */
-current_global_tls_state = state;
return OK;
}
store_pool = POOL_PERM;
state->ciphersuite = string_copy(cipherbuf);
store_pool = old_pool;
-tls_cipher = state->ciphersuite;
+state->tlsp->cipher = state->ciphersuite;
/* tls_peerdn */
cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
state->peerdn ? state->peerdn : US"<unset>");
}
-tls_peerdn = state->peerdn;
+state->tlsp->peerdn = state->peerdn;
return TRUE;
}
For inability to get SNI information, we return 0.
We only return non-zero if re-setup failed.
+Only used for server-side TLS.
*/
static int
{
char sni_name[MAX_HOST_LEN];
size_t data_len = MAX_HOST_LEN;
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = &state_server;
unsigned int sni_type;
int rc, old_pool;
store_pool = old_pool;
/* We set this one now so that variable expansions below will work */
-tls_sni = state->received_sni;
+state->tlsp->sni = state->received_sni;
DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", sni_name,
state->trigger_sni_changes ? "" : " (unused for certificate selection)");
exim_gnutls_state_st *state = NULL;
/* Check for previous activation */
-/* nb: this will not be TLS callout safe, needs reworking as part of that. */
-
-if (tls_active >= 0)
+if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", "", NULL);
smtp_printf("554 Already in TLS\r\n");
the response. Other smtp_printf() calls do not need it, because in non-TLS
mode, the fflush() happens when smtp_getc() is called. */
-if (!tls_on_connect)
+if (!state->tlsp->on_connect)
{
smtp_printf("220 TLS go ahead\r\n");
- fflush(smtp_out);
+ fflush(smtp_out); /*XXX JGH */
}
/* Now negotiate the TLS session. We put our own timer on it, since it seems
/* Sets various Exim expansion variables; always safe within server */
-extract_exim_vars_from_tls_state(state);
+extract_exim_vars_from_tls_state(state, TRUE);
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize appropriately. */
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
-extract_exim_vars_from_tls_state(state);
+extract_exim_vars_from_tls_state(state, FALSE);
return OK;
}
*/
void
-tls_close(BOOL shutdown)
+tls_close(BOOL is_server, BOOL shutdown)
{
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
-if (tls_active < 0) return; /* TLS was not active */
+if (state->tlsp->active < 0) return; /* TLS was not active */
if (shutdown)
{
exim_gnutls_base_init_done = FALSE;
}
-tls_active = -1;
+state->tlsp->active = -1;
}
/* This gets the next byte from the TLS input buffer. If the buffer is empty,
it refills the buffer via the GnuTLS reading function.
+Only used by the server-side TLS.
This feeds DKIM and should be used for all message-body reads.
int
tls_getc(void)
{
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = &state_server;
if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
{
ssize_t inbytes;
gnutls_deinit(state->session);
state->session = NULL;
- tls_active = -1;
- tls_bits = 0;
- tls_certificate_verified = FALSE;
- tls_channelbinding_b64 = NULL;
- tls_cipher = NULL;
- tls_peerdn = NULL;
+ state->tlsp->active = -1;
+ state->tlsp->bits = 0;
+ state->tlsp->certificate_verified = FALSE;
+ tls_channelbinding_b64 = NULL; /*XXX JGH */
+ state->tlsp->cipher = NULL;
+ state->tlsp->peerdn = NULL;
return smtp_getc();
}
/* This does not feed DKIM, so if the caller uses this for reading message body,
then the caller must feed DKIM.
+
Arguments:
buff buffer of data
len size of buffer
*/
int
-tls_read(uschar *buff, size_t len)
+tls_read(BOOL is_server, uschar *buff, size_t len)
{
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
ssize_t inbytes;
if (len > INT_MAX)
/*
Arguments:
+ is_server channel specifier
buff buffer of data
len number of bytes
*/
int
-tls_write(const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len)
{
ssize_t outbytes;
size_t left = len;
-exim_gnutls_state_st *state = current_global_tls_state;
+exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
while (left > 0)
static BOOL verify_callback_called = FALSE;
static const uschar *sid_ctx = US"exim";
-static SSL_CTX *ctx = NULL;
+static SSL_CTX *client_ctx = NULL;
+static SSL_CTX *server_ctx = NULL;
+static SSL *client_ssl = NULL;
+static SSL *server_ssl = NULL;
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
-static SSL_CTX *ctx_sni = NULL;
+static SSL_CTX *client_sni = NULL;
+static SSL_CTX *server_sni = NULL;
#endif
-static SSL *ssl = NULL;
static char ssl_errstring[256];
/* should figure out a cleanup of API to handle state preserved per
implementation, for various reasons, which can be void * in the APIs.
For now, we hack around it. */
-tls_ext_ctx_cb *static_cbinfo = NULL;
+tls_ext_ctx_cb *client_static_cbinfo = NULL;
+tls_ext_ctx_cb *server_static_cbinfo = NULL;
static int
setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
x509ctx->error_depth,
X509_verify_cert_error_string(x509ctx->error),
txt);
- tls_certificate_verified = FALSE;
+ tls_in.certificate_verified = FALSE;
verify_callback_called = TRUE;
if (!verify_optional) return 0; /* reject */
DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
{
DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
verify_callback_called? "" : " authenticated", txt);
- tls_peerdn = txt;
+ tls_in.peerdn = txt;
}
-if (!verify_callback_called) tls_certificate_verified = TRUE;
+if (!verify_callback_called) tls_in.certificate_verified = TRUE;
verify_callback_called = TRUE;
return 1; /* accept */
*/
static BOOL
+<<<<<<< HEAD
init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
+=======
+init_dh(SSL_CTX *ctx, uschar *dhparam, host_item *host)
+>>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling.
{
BIO *bio;
DH *dh;
/* Make the extension value available for expansion */
store_pool = POOL_PERM;
-tls_sni = string_copy(US servername);
+tls_in.sni = string_copy(US servername);
store_pool = old_pool;
if (!reexpand_tls_files_for_sni)
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */
-ctx_sni = SSL_CTX_new(SSLv23_server_method());
-if (!ctx_sni)
+server_sni = SSL_CTX_new(SSLv23_server_method());
+if (!server_sni)
{
ERR_error_string(ERR_get_error(), ssl_errstring);
DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
/* Not sure how many of these are actually needed, since SSL object
already exists. Might even need this selfsame callback, for reneg? */
-SSL_CTX_set_info_callback(ctx_sni, SSL_CTX_get_info_callback(ctx));
-SSL_CTX_set_mode(ctx_sni, SSL_CTX_get_mode(ctx));
-SSL_CTX_set_options(ctx_sni, SSL_CTX_get_options(ctx));
-SSL_CTX_set_timeout(ctx_sni, SSL_CTX_get_timeout(ctx));
-SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb);
-SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo);
+SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx));
+SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx));
+SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx));
+SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
+SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
+SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
if (cbinfo->server_cipher_list)
- SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list);
+ SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
#ifdef EXPERIMENTAL_OCSP
if (cbinfo->ocsp_file)
{
- SSL_CTX_set_tlsext_status_cb(ctx_sni, tls_stapling_cb);
+ SSL_CTX_set_tlsext_status_cb(server_sni, tls_stapling_cb);
SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
#endif
-rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
+rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
/* do this after setup_certs, because this can require the certs for verifying
OCSP information. */
-rc = tls_expand_session_files(ctx_sni, cbinfo);
+rc = tls_expand_session_files(server_sni, cbinfo);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
rc = init_dh(ctx_sni, cbinfo->dhparam, NULL);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
-SSL_set_SSL_CTX(s, ctx_sni);
+SSL_set_SSL_CTX(s, server_sni);
return SSL_TLSEXT_ERR_OK;
}
*/
static int
-tls_init(host_item *host, uschar *dhparam, uschar *certificate,
+tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
#ifdef EXPERIMENTAL_OCSP
uschar *ocsp_file,
#endif
- address_item *addr)
+ address_item *addr, tls_ext_ctx_cb ** cbp)
{
long init_options;
int rc;
By disabling with openssl_options, we can let admins re-enable with the
existing knob. */
-ctx = SSL_CTX_new((host == NULL)?
+*ctxp = SSL_CTX_new((host == NULL)?
SSLv23_server_method() : SSLv23_client_method());
-if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
+if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
/* It turns out that we need to seed the random number generator this early in
order to get the full complement of ciphers to work. It took me roughly a day
/* Set up the information callback, which outputs if debugging is at a suitable
level. */
-SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
/* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
/* Apply administrator-supplied work-arounds.
Historically we applied just one requested option,
if (init_options)
{
DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
- if (!(SSL_CTX_set_options(ctx, init_options)))
+ if (!(SSL_CTX_set_options(*ctxp, init_options)))
return tls_error(string_sprintf(
"SSL_CTX_set_option(%#lx)", init_options), host, NULL);
}
/* Initialize with DH parameters if supplied */
+<<<<<<< HEAD
if (!init_dh(ctx, dhparam, host)) return DEFER;
+=======
+if (!init_dh(*ctxp, dhparam, host)) return DEFER;
+>>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling.
/* Set up certificate and key (and perhaps OCSP info) */
-rc = tls_expand_session_files(ctx, cbinfo);
+rc = tls_expand_session_files(*ctxp, cbinfo);
if (rc != OK) return rc;
/* If we need to handle SNI, do so */
#endif
/* We always do this, so that $tls_sni is available even if not used in
tls_certificate */
- SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
- SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
+ SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
+ SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
}
#endif
/* Set up the RSA callback */
-SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
/* Finally, set the timeout, and we are done */
-SSL_CTX_set_timeout(ctx, ssl_session_timeout);
+SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
DEBUG(D_tls) debug_printf("Initialized TLS\n");
-static_cbinfo = cbinfo;
+*cbp = cbinfo;
return OK;
}
* Get name of cipher in use *
*************************************************/
-/* The answer is left in a static buffer, and tls_cipher is set to point
-to it.
-
+/*
Argument: pointer to an SSL structure for the connection
+ buffer to use for answer
+ size of buffer
+ pointer to number of bits for cipher
Returns: nothing
*/
static void
-construct_cipher_name(SSL *ssl)
+construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
{
-static uschar cipherbuf[256];
/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
the accessor functions use const in the prototype. */
}
c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
-SSL_CIPHER_get_bits(c, &tls_bits);
+SSL_CIPHER_get_bits(c, bits);
-string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
- SSL_CIPHER_get_name(c), tls_bits);
-tls_cipher = cipherbuf;
+string_format(cipherbuf, bsize, "%s:%s:%u", ver,
+ SSL_CIPHER_get_name(c), *bits);
DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
}
int rc;
uschar *expciphers;
tls_ext_ctx_cb *cbinfo;
+static uschar cipherbuf[256];
/* Check for previous activation */
-if (tls_active >= 0)
+if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", NULL, US"");
smtp_printf("554 Already in TLS\r\n");
/* Initialize the SSL library. If it fails, it will already have logged
the error. */
-rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey,
+rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
#ifdef EXPERIMENTAL_OCSP
tls_ocsp_file,
#endif
- NULL);
+ NULL, &server_static_cbinfo);
if (rc != OK) return rc;
-cbinfo = static_cbinfo;
+cbinfo = server_static_cbinfo;
if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
return FAIL;
uschar *s = expciphers;
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
- if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+ if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
cbinfo->server_cipher_list = expciphers;
}
/* If this is a host for which certificate verification is mandatory or
optional, set up appropriately. */
-tls_certificate_verified = FALSE;
+tls_in.certificate_verified = FALSE;
verify_callback_called = FALSE;
if (verify_check_host(&tls_verify_hosts) == OK)
{
- rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
if (rc != OK) return rc;
verify_optional = FALSE;
}
else if (verify_check_host(&tls_try_verify_hosts) == OK)
{
- rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
if (rc != OK) return rc;
verify_optional = TRUE;
}
/* Prepare for new connection */
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
+if ((server_ssl = SSL_new(server_ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
/* Warning: we used to SSL_clear(ssl) here, it was removed.
*
the response. Other smtp_printf() calls do not need it, because in non-TLS
mode, the fflush() happens when smtp_getc() is called. */
-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-if (!tls_on_connect)
+SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
+if (!tls_in.on_connect)
{
smtp_printf("220 TLS go ahead\r\n");
fflush(smtp_out);
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the OpenSSL library doesn't. */
-SSL_set_wfd(ssl, fileno(smtp_out));
-SSL_set_rfd(ssl, fileno(smtp_in));
-SSL_set_accept_state(ssl);
+SSL_set_wfd(server_ssl, fileno(smtp_out));
+SSL_set_rfd(server_ssl, fileno(smtp_in));
+SSL_set_accept_state(server_ssl);
DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-rc = SSL_accept(ssl);
+rc = SSL_accept(server_ssl);
alarm(0);
if (rc <= 0)
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize things. */
-construct_cipher_name(ssl);
+construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
+tls_in.cipher = cipherbuf;
DEBUG(D_tls)
{
uschar buf[2048];
- if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf)) != NULL)
+ if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
debug_printf("Shared ciphers: %s\n", buf);
}
+/* Only used by the server-side tls (tls_in), including tls_getc.
+ Client-side (tls_out) reads (seem to?) go via
+ smtp_read_response()/ip_recv().
+ Hence no need to duplicate for _in and _out.
+ */
ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
ssl_xfer_eof = ssl_xfer_error = 0;
receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered;
-tls_active = fileno(smtp_out);
+tls_in.active = fileno(smtp_out);
return OK;
}
uschar *expciphers;
X509* server_cert;
int rc;
+static uschar cipherbuf[256];
-rc = tls_init(host, dhparam, certificate, privatekey,
+rc = tls_init(&client_ctx, host, dhparam, certificate, privatekey,
#ifdef EXPERIMENTAL_OCSP
NULL,
#endif
- addr);
+ addr, &client_static_cbinfo);
if (rc != OK) return rc;
-tls_certificate_verified = FALSE;
+tls_out.certificate_verified = FALSE;
verify_callback_called = FALSE;
if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
uschar *s = expciphers;
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
- if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+ if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
}
-rc = setup_certs(ctx, verify_certs, crl, host, FALSE);
+rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE);
if (rc != OK) return rc;
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(ssl, fd);
-SSL_set_connect_state(ssl);
+if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
+SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(client_ssl, fd);
+SSL_set_connect_state(client_ssl);
if (sni)
{
- if (!expand_check(sni, US"tls_sni", &tls_sni))
+ if (!expand_check(sni, US"tls_sni", &tls_out.sni))
return FAIL;
- if (!Ustrlen(tls_sni))
- tls_sni = NULL;
+ if (!Ustrlen(tls_out.sni))
+ tls_out.sni = NULL;
else
{
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
- DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni);
- SSL_set_tlsext_host_name(ssl, tls_sni);
+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
+ SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
#else
DEBUG(D_tls)
debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
sigalrm_seen = FALSE;
alarm(timeout);
-rc = SSL_connect(ssl);
+rc = SSL_connect(client_ssl);
alarm(0);
if (rc <= 0)
DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
/* Beware anonymous ciphers which lead to server_cert being NULL */
-server_cert = SSL_get_peer_certificate (ssl);
+server_cert = SSL_get_peer_certificate (client_ssl);
if (server_cert)
{
- tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
+ tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
CS txt, sizeof(txt));
- tls_peerdn = txt;
+ tls_out.peerdn = txt;
}
else
- tls_peerdn = NULL;
+ tls_out.peerdn = NULL;
-construct_cipher_name(ssl); /* Sets tls_cipher */
+construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
+tls_out.cipher = cipherbuf;
-tls_active = fd;
+tls_out.active = fd;
return OK;
}
Arguments: none
Returns: the next character or EOF
+
+Only used by the server-side TLS.
*/
int
int error;
int inbytes;
- DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
+ DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
ssl_xfer_buffer, ssl_xfer_buffer_size);
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = SSL_read(ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
- error = SSL_get_error(ssl, inbytes);
+ inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
+ error = SSL_get_error(server_ssl, inbytes);
alarm(0);
/* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
receive_ferror = smtp_ferror;
receive_smtp_buffered = smtp_buffered;
- SSL_free(ssl);
- ssl = NULL;
- tls_active = -1;
- tls_bits = 0;
- tls_cipher = NULL;
- tls_peerdn = NULL;
- tls_sni = NULL;
+ SSL_free(server_ssl);
+ server_ssl = NULL;
+ tls_in.active = -1;
+ tls_in.bits = 0;
+ tls_in.cipher = NULL;
+ tls_in.peerdn = NULL;
+ tls_in.sni = NULL;
return smtp_getc();
}
Returns: the number of bytes read
-1 after a failed read
+
+Only used by the client-side TLS.
*/
int
int inbytes;
int error;
-DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", client_ssl,
buff, (unsigned int)len);
-inbytes = SSL_read(ssl, CS buff, len);
-error = SSL_get_error(ssl, inbytes);
+inbytes = SSL_read(client_ssl, CS buff, len);
+error = SSL_get_error(client_ssl, inbytes);
if (error == SSL_ERROR_ZERO_RETURN)
{
/*
Arguments:
+ is_server channel specifier
buff buffer of data
len number of bytes
Returns: the number of bytes after a successful write,
-1 after a failed write
+
+Used by both server-side and client-side TLS.
*/
int
-tls_write(const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len)
{
int outbytes;
int error;
int left = len;
+SSL *ssl = is_server ? server_ssl : client_ssl;
DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
while (left > 0)
log_write(0, LOG_MAIN, "SSL channel closed on write");
return -1;
+ case SSL_ERROR_SYSCALL:
+ log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+ sender_fullhost ? sender_fullhost : US"<unknown>",
+ strerror(errno));
+
default:
log_write(0, LOG_MAIN, "SSL_write error %d", error);
return -1;
Arguments: TRUE if SSL_shutdown is to be called
Returns: nothing
+
+Used by both server-side and client-side TLS.
*/
void
-tls_close(BOOL shutdown)
+tls_close(BOOL is_server, BOOL shutdown)
{
-if (tls_active < 0) return; /* TLS was not active */
+SSL **sslp = is_server ? &server_ssl : &client_ssl;
+
+if (*fdp < 0) return; /* TLS was not active */
if (shutdown)
{
DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
- SSL_shutdown(ssl);
+ SSL_shutdown(*sslp);
}
-SSL_free(ssl);
-ssl = NULL;
+SSL_free(*sslp);
+*sslp = NULL;
-tls_active = -1;
+*fdp = -1;
}
if (transport_write_timeout <= 0) /* No timeout wanted */
{
#ifdef SUPPORT_TLS
- if (tls_active == fd) rc = tls_write(block, len); else
+ if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
#endif
rc = write(fd, block, len);
save_errno = errno;
{
alarm(local_timeout);
#ifdef SUPPORT_TLS
- if (tls_active == fd) rc = tls_write(block, len); else
+ if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
#endif
rc = write(fd, block, len);
save_errno = errno;
int siglen = Ustrlen(dkim_signature);
while(siglen > 0) {
#ifdef SUPPORT_TLS
- if (tls_active == fd) wwritten = tls_write(dkim_signature, siglen); else
+ if (tls_out.active == fd) wwritten = tls_write(FALSE, dkim_signature, siglen); else
#endif
wwritten = write(fd,dkim_signature,siglen);
if (wwritten == -1) {
to the socket. However only if we don't use TLS,
in which case theres another layer of indirection
before the data finally hits the socket. */
- if (tls_active != fd)
+ if (tls_out.active != fd)
{
ssize_t copied = 0;
off_t offset = 0;
/* write the chunk */
DKIM_WRITE:
#ifdef SUPPORT_TLS
- if (tls_active == fd) wwritten = tls_write(US p, sread); else
+ if (tls_out.active == fd) wwritten = tls_write(FALSE, US p, sread); else
#endif
wwritten = write(fd,p,sread);
if (wwritten == -1)
/* Reset the parameters of a TLS session. */
-tls_bits = 0;
-tls_cipher = NULL;
-tls_peerdn = NULL;
+tls_in.bits = 0;
+tls_in.cipher = NULL; /* for back-compatible behaviour */
+tls_in.peerdn = NULL;
#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
-tls_sni = NULL;
+tls_in.sni = NULL;
+#endif
+
+tls_out.bits = 0;
+tls_out.cipher = NULL; /* the one we may use for this transport */
+tls_out.peerdn = NULL;
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+tls_out.sni = NULL;
#endif
/* If an authenticated_sender override has been specified for this transport
{
if (addr->transport_return == PENDING_DEFER)
{
- addr->cipher = tls_cipher;
- addr->peerdn = tls_peerdn;
+ addr->cipher = tls_out.cipher;
+ addr->peerdn = tls_out.peerdn;
}
}
}
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_active >= 0)
+if (tls_out.active >= 0)
{
char *greeting_cmd;
if (helo_data == NULL)
if (continue_hostname == NULL
#ifdef SUPPORT_TLS
- || tls_active >= 0
+ || tls_out.active >= 0
#endif
)
{
BOOL more;
if (first_addr != NULL || continue_more ||
(
- (tls_active < 0 ||
+ (tls_out.active < 0 ||
verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
host->address, NULL) != OK)
&&
don't get a good response, we don't attempt to pass the socket on. */
#ifdef SUPPORT_TLS
- if (tls_active >= 0)
+ if (tls_out.active >= 0)
{
- tls_close(TRUE);
+ tls_close(FALSE, TRUE);
if (smtps)
ok = FALSE;
else
END_OFF:
#ifdef SUPPORT_TLS
-tls_close(TRUE);
+tls_close(FALSE, TRUE);
#endif
/* Close the socket, and return the appropriate value, first setting
#include "exim.h"
+#include "transports/smtp.h"
#define CUTTHROUGH_CMD_TIMEOUT 30 /* timeout for cutthrough-routing calls */
#define CUTTHROUGH_DATA_TIMEOUT 60 /* timeout for cutthrough-routing calls */
address_item cutthrough_addr;
+static smtp_outblock ctblock;
+uschar ctbuffer[8192];
+
/* Structure for caching DNSBL lookups */
int callout, int callout_overall, int callout_connect, int options,
uschar *se_mailfrom, uschar *pm_mailfrom)
{
+smtp_transport_options_block *ob = (smtp_transport_options_block *)(addr->transport->options_block);
BOOL is_recipient = (options & vopt_is_recipient) != 0;
BOOL callout_no_cache = (options & vopt_callout_no_cache) != 0;
BOOL callout_random = (options & vopt_callout_random) != 0;
if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
+/* Precompile some regex that are used to recognize parameters in response
+to an EHLO command, if they aren't already compiled. */
+#ifdef SUPPORT_TLS
+if (regex_STARTTLS == NULL) regex_STARTTLS =
+ regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
/* Now make connections to the hosts and do real callouts. The list of hosts
is passed in as an argument. */
int port = 25;
BOOL send_quit = TRUE;
uschar *active_hostname = smtp_active_hostname;
- uschar *helo = US"HELO";
+ BOOL lmtp;
+ BOOL smtps;
+ BOOL esmtp;
+ BOOL suppress_tls = FALSE;
uschar *interface = NULL; /* Outgoing interface to use; NULL => any */
uschar inbuffer[4096];
uschar outbuffer[1024];
addr->message);
/* Set HELO string according to the protocol */
+ lmtp= Ustrcmp(tf->protocol, "lmtp") == 0;
+ smtps= Ustrcmp(tf->protocol, "smtps") == 0;
- if (Ustrcmp(tf->protocol, "lmtp") == 0) helo = US"LHLO";
HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);
outblock.cmd_count = 0;
outblock.authenticating = FALSE;
+ /* Reset the parameters of a TLS session */
+ tls_out.cipher = tls_out.peerdn = NULL;
+
/* Connect to the host; on failure, just loop for the next one, but we
set the error for the last one. Use the callout_connect timeout. */
+ tls_retry_connection:
+
inblock.sock = outblock.sock =
smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
/* reconsider DSCP here */
Ustrcpy(big_buffer, "initial connection");
- done =
- smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
- '2', callout) &&
- smtp_write_command(&outblock, FALSE, "%s %s\r\n", helo,
- active_hostname) >= 0 &&
- smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
- '2', callout);
+ /* Unless ssl-on-connect, wait for the initial greeting */
+ smtps_redo_greeting:
+
+ #ifdef SUPPORT_TLS
+ if (!smtps || (smtps && tls_out.active >= 0))
+ #endif
+ if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
+ goto RESPONSE_FAILED;
+
+ /* Not worth checking greeting line for ESMTP support */
+ if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
+ host->name, host->address, NULL) != OK))
+ DEBUG(D_transport)
+ debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+
+ tls_redo_helo:
+
+ #ifdef SUPPORT_TLS
+ if (smtps && tls_out.active < 0) /* ssl-on-connect, first pass */
+ {
+ tls_offered = TRUE;
+ ob->tls_tempfail_tryclear = FALSE;
+ }
+ else /* all other cases */
+ #endif
+
+ { esmtp_retry:
+
+ if (!(done= smtp_write_command(&outblock, FALSE, "%s %s\r\n",
+ !esmtp? "HELO" : lmtp? "LHLO" : "EHLO", active_hostname) >= 0))
+ goto SEND_FAILED;
+ if (!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))
+ {
+ if (errno != 0 || responsebuffer[0] == 0 || lmtp || !esmtp || tls_out.active >= 0)
+ {
+ done= FALSE;
+ goto RESPONSE_FAILED;
+ }
+ #ifdef SUPPORT_TLS
+ tls_offered = FALSE;
+ #endif
+ esmtp = FALSE;
+ goto esmtp_retry; /* fallback to HELO */
+ }
+
+ /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
+ #ifdef SUPPORT_TLS
+ tls_offered = esmtp && !suppress_tls && tls_out.active < 0 &&
+ pcre_exec(regex_STARTTLS, NULL, CS responsebuffer, Ustrlen(responsebuffer), 0,
+ PCRE_EOPT, NULL, 0) >= 0;
+ #endif
+ }
+
+ /* If TLS is available on this connection 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 (tls_offered &&
+ verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
+ host->address, NULL) != OK)
+ {
+ uschar buffer2[4096];
+ if ( !smtps
+ && !(done= smtp_write_command(&outblock, FALSE, "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 (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
+ ob->command_timeout))
+ {
+ if (errno != 0 || buffer2[0] == 0 ||
+ (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+ {
+ Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
+ done= FALSE;
+ goto RESPONSE_FAILED;
+ }
+ }
+
+ /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
+ else
+ {
+ int rc = tls_client_start(inblock.sock, host, addr,
+ NULL, /* No DH param */
+ ob->tls_certificate, ob->tls_privatekey,
+ ob->tls_sni,
+ ob->tls_verify_certificates, ob->tls_crl,
+ ob->tls_require_ciphers,
+ ob->gnutls_require_mac, ob->gnutls_require_kx, ob->gnutls_require_proto,
+ callout);
+
+ /* TLS negotiation failed; give an error. Try in clear on a new connection,
+ if the options permit it for this host. */
+ if (rc != OK)
+ {
+ if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
+ verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+ host->address, NULL) != OK)
+ {
+ (void)close(inblock.sock);
+ log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
+ "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+ suppress_tls = TRUE;
+ goto tls_retry_connection;
+ }
+ /*save_errno = ERRNO_TLSFAILURE;*/
+ /*message = US"failure while setting up TLS session";*/
+ send_quit = FALSE;
+ done= FALSE;
+ goto TLS_FAILED;
+ }
+
+ /* TLS session is set up. Copy info for logging. */
+ addr->cipher = tls_out.cipher;
+ addr->peerdn = tls_out.peerdn;
+
+ /* For SMTPS we need to wait for the initial OK response, then do HELO. */
+ if (smtps)
+ goto smtps_redo_greeting;
+
+ /* For STARTTLS we need to redo EHLO */
+ goto tls_redo_helo;
+ }
+ }
+
+ /* If the host is required to use a secure channel, ensure that we have one. */
+ if (tls_out.active < 0)
+ if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+ host->address, NULL) == OK)
+ {
+ /*save_errno = ERRNO_TLSREQUIRED;*/
+ log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
+ host->name, host->address,
+ tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
+ done= FALSE;
+ goto TLS_FAILED;
+ }
+
+ #endif /*SUPPORT_TLS*/
+
+ done = TRUE; /* so far so good; have response to HELO */
+
+ /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING, AUTH */
+ /* If we haven't authenticated, but are required to, give up. */
+
+ /*XXX "filter command specified for this transport" ??? */
+ /* for now, transport_filter by cutthrough-delivery is not supported */
+ /* Need proper integration with the proper transport mechanism. */
+
+
+ SEND_FAILED:
+ RESPONSE_FAILED:
+ TLS_FAILED:
+ ;
+ /* Clear down of the TLS, SMTP and TCP layers on error is handled below. */
+
/* Failure to accept HELO is cached; this blocks the whole domain for all
senders. I/O errors and defer responses are not cached. */
{
/*XXX not suitable for cutthrough - sequencing problems */
cutthrough_delivery= FALSE;
+ HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");
done =
smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
/* End the SMTP conversation and close the connection. */
- /*XXX cutthrough - if "done"
- and "yeild" is OK
+ /* Cutthrough - on a successfull connect and recipient-verify with use-sender
and we have no cutthrough conn so far
here is where we want to leave the conn open */
- /* and leave some form of marker for it */
- /*XXX in fact for simplicity we should abandon cutthrough as soon as more than one address
- comes into play */
-/*XXX what about TLS? */
if ( cutthrough_delivery
&& done
&& yield == OK
- && cutthrough_fd < 0
&& (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
&& !random_local_part
&& !pm_mailfrom
+ && cutthrough_fd < 0
)
{
cutthrough_fd= outblock.sock; /* We assume no buffer in use in the outblock */
- cutthrough_addr= *addr; /* Save the address_item for later logging */
+ cutthrough_addr = *addr; /* Save the address_item for later logging */
+ cutthrough_addr.host_used = store_get(sizeof(host_item));
+ cutthrough_addr.host_used->name = host->name;
+ cutthrough_addr.host_used->address = host->address;
+ cutthrough_addr.host_used->port = port;
+ if (addr->parent)
+ *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
+ ctblock.buffer = ctbuffer;
+ ctblock.buffersize = sizeof(ctbuffer);
+ ctblock.ptr = ctbuffer;
+ /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
+ ctblock.sock = cutthrough_fd;
}
else
{
+ if (options & vopt_callout_recipsender)
+ cancel_cutthrough_connection(); /* Ensure no cutthrough on multiple address verifies */
if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+
+ #ifdef SUPPORT_TLS
+ tls_close(FALSE, TRUE);
+ #endif
(void)close(inblock.sock);
}
+/* Called after recipient-acl to get a cutthrough connection open when
+ one was requested and a recipient-verify wasn't subsequently done.
+*/
void
open_cutthrough_connection( address_item * addr )
{
}
-static smtp_outblock ctblock;
-uschar ctbuffer[8192];
-
-void
-cancel_cutthrough_connection( void )
+/* Send given number of bytes from the buffer */
+static BOOL
+cutthrough_send(int n)
{
-ctblock.ptr = ctbuffer;
-cutthrough_delivery= FALSE;
-if(cutthrough_fd >= 0) /*XXX get that initialised, also at RSET */
- {
- int rc;
+if(cutthrough_fd < 0)
+ return TRUE;
- /* We could be sending this after a bunch of data, but that is ok as
- the only way to cancel the transfer in dataphase is to drop the tcp
- conn before the final dot.
- */
- HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> QUIT\n");
- rc= send(cutthrough_fd, "QUIT\r\n", 6, 0);
- /*XXX error handling? TLS? See flush_buffer() in smtp_out.c */
+if(
+#ifdef SUPPORT_TLS
+ (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+#endif
+ send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+ )
+{
+ transport_count += n;
+ ctblock.ptr= ctblock.buffer;
+ return TRUE;
+}
- (void)close(cutthrough_fd);
- cutthrough_fd= -1;
- HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n");
- }
+HDEBUG(D_transport|D_acl) debug_printf("cutthrough_send failed: %s\n", strerror(errno));
+return FALSE;
}
-/* Buffered output counted data block. Return boolean success */
+static BOOL
+_cutthrough_puts(uschar * cp, int n)
+{
+while(n--)
+ {
+ if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
+ if(!cutthrough_send(ctblock.buffersize))
+ return FALSE;
+
+ *ctblock.ptr++ = *cp++;
+ }
+return TRUE;
+}
+
+/* Buffered output of counted data block. Return boolean success */
BOOL
cutthrough_puts(uschar * cp, int n)
{
-if(cutthrough_fd >= 0)
- while(n--)
- {
- /*XXX TLS? See flush_buffer() in smtp_out.c */
+if (cutthrough_fd < 0) return TRUE;
+if (_cutthrough_puts(cp, n)) return TRUE;
+cancel_cutthrough_connection();
+return FALSE;
+}
- if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
- {
- if(send(cutthrough_fd, ctblock.buffer, ctblock.buffersize, 0) < 0)
- goto bad;
- transport_count += ctblock.buffersize;
- ctblock.ptr= ctblock.buffer;
- }
- *ctblock.ptr++ = *cp++;
- }
-return TRUE;
+static BOOL
+_cutthrough_flush_send( void )
+{
+int n= ctblock.ptr-ctblock.buffer;
-bad:
-cancel_cutthrough_connection();
-return FALSE;
+if(n>0)
+ if(!cutthrough_send(n))
+ return FALSE;
+return TRUE;
}
+
+/* Send out any bufferred output. Return boolean success. */
BOOL
cutthrough_flush_send( void )
{
-if(cutthrough_fd >= 0)
- {
- if(send(cutthrough_fd, ctblock.buffer, ctblock.ptr-ctblock.buffer, 0) < 0)
- goto bad;
- transport_count += ctblock.ptr-ctblock.buffer;
- ctblock.ptr= ctblock.buffer;
- }
-return TRUE;
-
-bad:
+if (_cutthrough_flush_send()) return TRUE;
cancel_cutthrough_connection();
return FALSE;
}
inblock.ptr = inbuffer;
inblock.ptrend = inbuffer;
inblock.sock = cutthrough_fd;
+/* this relies on (inblock.sock == tls_out.active) */
if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
cancel_cutthrough_connection();
BOOL
cutthrough_predata( void )
{
-int rc;
-
if(cutthrough_fd < 0)
return FALSE;
HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n");
-rc= send(cutthrough_fd, "DATA\r\n", 6, 0);
-if (rc <= 0)
- {
- HDEBUG(D_transport|D_acl) debug_printf("send failed: %s\n", strerror(errno));
- cancel_cutthrough_connection();
- return FALSE;
- }
-/*XXX error handling? TLS? See flush_buffer() in smtp_out.c */
+cutthrough_puts(US"DATA\r\n", 6);
+cutthrough_flush_send();
/* Assume nothing buffered. If it was it gets ignored. */
return cutthrough_response('3', NULL) == '3';
/* Buffered send of headers. Return success boolean. */
+/* Expands newlines to wire format (CR,NL). */
/* Also sends header-terminating blank line. */
-/* Sets up the "ctblock" buffer as a side-effect. */
BOOL
cutthrough_headers_send( void )
{
header_line * h;
+uschar * cp1, * cp2;
if(cutthrough_fd < 0)
return FALSE;
-ctblock.buffer = ctbuffer;
-ctblock.buffersize = sizeof(ctbuffer);
-ctblock.ptr = ctbuffer;
-/* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
-ctblock.sock = cutthrough_fd;
-
for(h= header_list; h != NULL; h= h->next)
if(h->type != htype_old && h->text != NULL)
- if(!cutthrough_puts(h->text, h->slen))
- return FALSE;
+ for (cp1 = h->text; *cp1 && (cp2 = Ustrchr(cp1, '\n')); cp1 = cp2+1)
+ if( !cutthrough_puts(cp1, cp2-cp1)
+ || !cutthrough_put_nl())
+ return FALSE;
-if(!cutthrough_put_nl())
- return TRUE;
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>>(nl)\n");
+return cutthrough_put_nl();
}
+static void
+close_cutthrough_connection( void )
+{
+if(cutthrough_fd >= 0)
+ {
+ /* We could be sending this after a bunch of data, but that is ok as
+ the only way to cancel the transfer in dataphase is to drop the tcp
+ conn before the final dot.
+ */
+ ctblock.ptr = ctbuffer;
+ HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> QUIT\n");
+ _cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */
+ _cutthrough_flush_send();
+ /* No wait for response */
+
+ #ifdef SUPPORT_TLS
+ tls_close(FALSE, TRUE);
+ #endif
+ (void)close(cutthrough_fd);
+ cutthrough_fd= -1;
+ HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n");
+ }
+ctblock.ptr = ctbuffer;
+}
+
+void
+cancel_cutthrough_connection( void )
+{
+close_cutthrough_connection();
+cutthrough_delivery= FALSE;
+}
+
+
+
+
/* Have senders final-dot. Send one to cutthrough target, and grab the response.
Log an OK response as a transmission.
+ Close the connection.
Return smtp response-class digit.
- XXX where do fail responses from target get logged?
*/
uschar *
cutthrough_finaldot( void )
HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> .\n");
/* Assume data finshed with new-line */
-if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl()
- || !cutthrough_flush_send()
- || cutthrough_response('2', &cutthrough_addr.message) != '2')
+if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send())
return cutthrough_addr.message;
-(void)close(cutthrough_fd);
-cutthrough_fd= -1;
-HDEBUG(D_acl) debug_printf("----------- cutthrough close ------------\n");
+switch(cutthrough_response('2', &cutthrough_addr.message))
+ {
+ case '2':
+ delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
+ close_cutthrough_connection();
+ break;
+
+ case '4':
+ delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:");
+ break;
-delivery_log(&cutthrough_addr, (int)'>');
-/* C= ok */
-/* QT ok */
-/* DT always 0? */
-/* delivery S= zero! (transport_count) */
-/* not TLS yet hence no X, CV, DN */
+ case '5':
+ delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:");
+ break;
-return cutthrough_addr.message;
+ default:
+ break;
+ }
+ return cutthrough_addr.message;
}
+
/*************************************************
* Copy error to toplevel address *
*************************************************/