From: Jeremy Harris Date: Sun, 29 Apr 2012 20:02:27 +0000 (+0100) Subject: Dual-tls - split management of TLS into in- and out-bound connection-handling. X-Git-Tag: exim-4_81_RC1~80 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/817d9f576cdfbc27cf0536be348645baf27d7836 Dual-tls - split management of TLS into in- and out-bound connection-handling. 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. --- diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c index 816d42d05..366af7e97 100644 --- a/src/exim_monitor/em_globals.c +++ b/src/exim_monitor/em_globals.c @@ -211,12 +211,15 @@ int string_datestamp_length= 0; 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; diff --git a/src/src/acl.c b/src/src/acl.c index d0688d19a..3177ac842 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -1615,7 +1615,7 @@ switch(vp->value) 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; @@ -3166,11 +3166,11 @@ for (; cb != NULL; cb = cb->next) 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; @@ -3915,21 +3915,14 @@ if (where == ACL_WHERE_RCPT) 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. @@ -3940,10 +3933,6 @@ On data acl accept, terminate the dataphase on an open cutthrough conn. If acce 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) { diff --git a/src/src/auths/cyrus_sasl.c b/src/src/auths/cyrus_sasl.c index 7922363ec..ca589287e 100644 --- a/src/src/auths/cyrus_sasl.c +++ b/src/src/auths/cyrus_sasl.c @@ -256,19 +256,19 @@ if( rc != SASL_OK ) 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"); diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c index ba0b8943a..0824240a0 100644 --- a/src/src/auths/dovecot.c +++ b/src/src/auths/dovecot.c @@ -241,10 +241,10 @@ int auth_dovecot_server(auth_instance *ablock, uschar *data) /* 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"; diff --git a/src/src/daemon.c b/src/src/daemon.c index 9385a91f4..3467f14a7 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -382,7 +382,7 @@ if (pid == 0) /* 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 @@ -639,7 +639,7 @@ if (pid == 0) 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. */ diff --git a/src/src/deliver.c b/src/src/deliver.c index b4d0251a7..55a27b023 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -673,8 +673,15 @@ while (addr->parent != NULL) +/* 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, */ @@ -689,12 +696,17 @@ have a pointer to the host item that succeeded; local deliveries can have a 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 @@ -711,8 +723,10 @@ if (used_return_path != NULL && (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); @@ -796,7 +810,7 @@ if ((log_extra_selector & LX_deliver_time) != 0) 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; } @@ -992,7 +1006,7 @@ if (result == OK) child_done(addr, now); } - delivery_log(addr, logchar); + delivery_log(LOG_MAIN, addr, logchar, NULL); } @@ -3948,7 +3962,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) /* 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 */ diff --git a/src/src/exim.c b/src/src/exim.c index f50cc0814..a59cfea9a 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -526,7 +526,7 @@ close_unwanted(void) 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)); @@ -3276,7 +3276,7 @@ for (i = 1; i < argc; i++) /* -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; @@ -4093,7 +4093,7 @@ if (smtp_input) 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) { diff --git a/src/src/expand.c b/src/src/expand.c index 84167b688..3fb431c2e 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -612,13 +612,22 @@ static var_entry var_table[] = { { "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 }, diff --git a/src/src/functions.h b/src/src/functions.h index aa3d18404..250043193 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -28,15 +28,15 @@ extern const char * 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 @@ -93,7 +93,7 @@ extern void debug_vprintf(const char *, va_list); 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 *); diff --git a/src/src/globals.c b/src/src/globals.c index 60b5a27d7..97c7166ab 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -93,13 +93,27 @@ BOOL move_frozen_messages = FALSE; 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; @@ -123,7 +137,6 @@ BOOL tls_offered = 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; diff --git a/src/src/globals.h b/src/src/globals.h index dd28b914b..7ed9d5ab6 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -74,13 +74,20 @@ extern BOOL move_frozen_messages; /* Get them out of the normal directory * 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 */ @@ -102,7 +109,6 @@ extern BOOL tls_offered; /* Server offered TLS */ 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 */ diff --git a/src/src/host.c b/src/src/host.c index 03d944334..785eea412 100644 --- a/src/src/host.c +++ b/src/src/host.c @@ -1177,10 +1177,10 @@ host_is_tls_on_connect_port(int port) { 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) { diff --git a/src/src/ip.c b/src/src/ip.c index 11f0fd88b..b0c98878b 100644 --- a/src/src/ip.c +++ b/src/src/ip.c @@ -347,8 +347,10 @@ for (;;) 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); diff --git a/src/src/macros.h b/src/src/macros.h index f7a22b668..b17a80e10 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -81,7 +81,7 @@ as unsigned. */ 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 diff --git a/src/src/readconf.c b/src/src/readconf.c index 553f2e455..750e0d316 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -418,10 +418,10 @@ static optionlist optionlist_config[] = { { "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 }, diff --git a/src/src/receive.c b/src/src/receive.c index 108e8d436..d0fc0c25e 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -716,9 +716,6 @@ Arguments: 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) @@ -1372,6 +1369,7 @@ BOOL resents_exist = FALSE; 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; @@ -1415,8 +1413,7 @@ search_tidyup(); /* 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(); @@ -2038,7 +2035,6 @@ for (h = header_list->next; h != NULL; h = h->next) case htype_received: h->type = htype_received; -/*XXX cutthrough delivery - need to error on excessive number here */ received_count++; break; @@ -2710,22 +2706,34 @@ if (filter_test != FTEST_NONE) 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 @@ -2783,7 +2791,6 @@ if (next != NULL) { 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 */ } @@ -2792,13 +2799,10 @@ if (next != NULL) (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 */ } @@ -2869,7 +2873,6 @@ we can then give up. Note that for SMTP input we must swallow the remainder of 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)()) { @@ -2909,7 +2912,6 @@ if (fflush(data_file) == EOF || ferror(data_file) || /* 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); @@ -2924,9 +2926,6 @@ recipients or stderr error writing, throw the data file away afterwards, and 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) @@ -3022,13 +3021,9 @@ if (received_header->text == NULL) /* Non-cutthrough case */ 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 @@ -3056,7 +3051,6 @@ else #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(); @@ -3161,7 +3155,6 @@ else #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)) @@ -3171,9 +3164,6 @@ else /* 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); @@ -3300,7 +3290,6 @@ local_scan_data = NULL; 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); @@ -3456,7 +3445,6 @@ deliver_firsttime = TRUE; #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); }; @@ -3498,9 +3486,6 @@ if (host_checking || blackholed_by != NULL) 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); @@ -3567,18 +3552,18 @@ if (message_reference != NULL) 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) @@ -3759,63 +3744,41 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket && /* 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) { @@ -3884,7 +3847,6 @@ if (smtp_input) 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) @@ -3921,11 +3883,24 @@ if (smtp_input) 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 diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 9652a06e6..4789fbae0 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -455,9 +455,10 @@ if (rcpt_in_progress) /* 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 @@ -483,7 +484,7 @@ Returns: 0 for no error; -1 after an error 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; } @@ -506,7 +507,7 @@ command_timeout_handler(int sig) 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 */ @@ -707,7 +708,7 @@ fd_set fds; 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); @@ -850,18 +851,18 @@ if (sender_host_authenticated != NULL) } #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)? @@ -1404,7 +1405,7 @@ if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL; authenticated_by = NULL; #ifdef SUPPORT_TLS -tls_cipher = tls_peerdn = NULL; +tls_in.cipher = tls_in.peerdn = NULL; tls_advertised = FALSE; #endif @@ -1692,8 +1693,7 @@ if (!sender_host_unknown) 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 @@ -2808,7 +2808,7 @@ while (done <= 0) 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; @@ -2942,7 +2942,7 @@ while (done <= 0) 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, @@ -3167,7 +3167,7 @@ while (done <= 0) 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); @@ -3188,7 +3188,7 @@ while (done <= 0) 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); @@ -3206,9 +3206,9 @@ while (done <= 0) 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); @@ -3914,7 +3914,7 @@ while (done <= 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 */ } @@ -3975,7 +3975,7 @@ while (done <= 0) } /* 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"); @@ -4019,7 +4019,7 @@ while (done <= 0) break; } } - tls_close(TRUE); + tls_close(TRUE, TRUE); break; #endif @@ -4044,7 +4044,7 @@ while (done <= 0) smtp_respond(US"221", 3, TRUE, user_msg); #ifdef SUPPORT_TLS - tls_close(TRUE); + tls_close(TRUE, TRUE); #endif done = 2; @@ -4082,7 +4082,7 @@ while (done <= 0) 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 diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 42a889aa6..0fa4ccd48 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -300,8 +300,8 @@ flush_buffer(smtp_outblock *outblock) 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 diff --git a/src/src/spool_in.c b/src/src/spool_in.c index 3c611a505..674780475 100644 --- a/src/src/spool_in.c +++ b/src/src/spool_in.c @@ -283,10 +283,10 @@ dkim_collect_input = FALSE; #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 @@ -545,13 +545,13 @@ for (;;) #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 diff --git a/src/src/spool_out.c b/src/src/spool_out.c index 1a380dd17..5df2a414e 100644 --- a/src/src/spool_out.c +++ b/src/src/spool_out.c @@ -226,10 +226,10 @@ if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts); #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 diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index cf315b6d1..8a133c5af 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -63,8 +63,7 @@ Some of these correspond to variables in globals.c; those variables will 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 { @@ -95,6 +94,8 @@ 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; @@ -107,6 +108,7 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, 0, 0, 0, 0, }; @@ -120,7 +122,6 @@ there's no way for heart-beats to be responded to, for the duration of the 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 @@ -286,15 +287,13 @@ Sets: 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 @@ -303,19 +302,17 @@ int rc; 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. */ @@ -336,9 +333,8 @@ if (rc) { } #endif -tls_peerdn = state->peerdn; - -tls_sni = state->received_sni; +state->tlsp->peerdn = state->peerdn; +state->tlsp->sni = state->received_sni; } @@ -884,6 +880,7 @@ Arguments: cas CA certs file crl CRL file require_ciphers tls_require_ciphers setting + caller_state returned state-info structure Returns: OK/DEFER/FAIL */ @@ -929,6 +926,7 @@ if (host) { 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); } @@ -936,6 +934,7 @@ else { 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); } @@ -967,7 +966,7 @@ if (rc != OK) return rc; /* 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) { @@ -1038,8 +1037,6 @@ if (gnutls_compat_mode) } *caller_state = state; -/* needs to happen before callbacks during handshake */ -current_global_tls_state = state; return OK; } @@ -1118,7 +1115,7 @@ old_pool = store_pool; 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); @@ -1240,7 +1237,7 @@ else state->peerdn ? state->peerdn : US""); } -tls_peerdn = state->peerdn; +state->tlsp->peerdn = state->peerdn; return TRUE; } @@ -1284,6 +1281,7 @@ handshake.". 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 @@ -1291,7 +1289,7 @@ exim_sni_handling_cb(gnutls_session_t session) { 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; @@ -1321,7 +1319,7 @@ state->received_sni = string_copyn(US sni_name, data_len); 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)"); @@ -1377,9 +1375,7 @@ const char *error; 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"); @@ -1430,10 +1426,10 @@ make them disconnect. We need to have an explicit fflush() here, to force out 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 @@ -1500,7 +1496,7 @@ if (rc != OK) return rc; /* 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. */ @@ -1620,7 +1616,7 @@ if (rc != OK) return rc; /* 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; } @@ -1641,11 +1637,11 @@ Returns: nothing */ 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) { @@ -1663,7 +1659,7 @@ if ((state_server.session == NULL) && (state_client.session == NULL)) exim_gnutls_base_init_done = FALSE; } -tls_active = -1; +state->tlsp->active = -1; } @@ -1675,6 +1671,7 @@ tls_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. @@ -1685,7 +1682,7 @@ Returns: the next character or EOF 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; @@ -1714,12 +1711,12 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) 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(); } @@ -1753,6 +1750,7 @@ return state->xfer_buffer[state->xfer_buffer_lwm++]; /* 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 @@ -1762,9 +1760,9 @@ Returns: the number of bytes read */ 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) @@ -1800,6 +1798,7 @@ return -1; /* Arguments: + is_server channel specifier buff buffer of data len number of bytes @@ -1808,11 +1807,11 @@ Returns: the number of bytes after a successful write, */ 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) diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index fdcb95ef2..31405606c 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -45,11 +45,14 @@ typedef struct randstuff { 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]; @@ -77,7 +80,8 @@ typedef struct tls_ext_ctx_cb { /* 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); @@ -214,7 +218,7 @@ if (state == 0) 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 " @@ -231,10 +235,10 @@ else { 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 */ @@ -282,7 +286,11 @@ Returns: TRUE if OK (nothing to set up, or setup worked) */ 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; @@ -599,7 +607,7 @@ DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername, /* 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) @@ -609,8 +617,8 @@ 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); @@ -620,35 +628,35 @@ if (!ctx_sni) /* 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; } @@ -714,12 +722,12 @@ Returns: OK/DEFER/FAIL */ 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; @@ -752,10 +760,10 @@ when OpenSSL is built without SSLv2 support. 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 @@ -783,10 +791,10 @@ if (!RAND_status()) /* 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, @@ -804,7 +812,7 @@ if (!okay) 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); } @@ -813,11 +821,15 @@ else /* 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 */ @@ -837,21 +849,21 @@ if (host == NULL) #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; } @@ -863,17 +875,17 @@ 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. */ @@ -911,11 +923,10 @@ switch (ssl->session->ssl_version) } 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); } @@ -1072,10 +1083,11 @@ tls_server_start(const uschar *require_ciphers) 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"); @@ -1085,13 +1097,13 @@ if (tls_active >= 0) /* 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; @@ -1106,7 +1118,7 @@ if (expciphers != NULL) 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; } @@ -1114,25 +1126,25 @@ if (expciphers != NULL) /* 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. * @@ -1153,8 +1165,8 @@ make them disconnect. We need to have an explicit fflush() here, to force out 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); @@ -1163,15 +1175,15 @@ if (!tls_on_connect) /* 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) @@ -1188,16 +1200,22 @@ DEBUG(D_tls) debug_printf("SSL_accept was successful\n"); /* 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; @@ -1208,7 +1226,7 @@ receive_feof = tls_feof; receive_ferror = tls_ferror; receive_smtp_buffered = tls_smtp_buffered; -tls_active = fileno(smtp_out); +tls_in.active = fileno(smtp_out); return OK; } @@ -1252,15 +1270,16 @@ static uschar txt[256]; 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)) @@ -1275,29 +1294,29 @@ if (expciphers != NULL) 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", @@ -1311,7 +1330,7 @@ if (sni) 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) @@ -1320,19 +1339,20 @@ 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; } @@ -1349,6 +1369,8 @@ it refills the buffer via the SSL reading function. Arguments: none Returns: the next character or EOF + +Only used by the server-side TLS. */ int @@ -1359,12 +1381,12 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) 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 @@ -1381,13 +1403,13 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) 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(); } @@ -1434,6 +1456,8 @@ Arguments: Returns: the number of bytes read -1 after a failed read + +Only used by the client-side TLS. */ int @@ -1442,11 +1466,11 @@ tls_read(uschar *buff, size_t len) 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) { @@ -1471,19 +1495,23 @@ return inbytes; /* 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) @@ -1508,6 +1536,11 @@ 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"", + strerror(errno)); + default: log_write(0, LOG_MAIN, "SSL_write error %d", error); return -1; @@ -1528,23 +1561,27 @@ would tamper with the SSL session in the parent process). 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; } diff --git a/src/src/transport.c b/src/src/transport.c index 6894e96df..9e533f63e 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -219,7 +219,7 @@ for (i = 0; i < 100; i++) 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; @@ -231,7 +231,7 @@ for (i = 0; i < 100; i++) { 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; @@ -1046,7 +1046,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options, 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) { @@ -1072,7 +1072,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options, 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; @@ -1096,7 +1096,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options, /* 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) diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 5322cc58d..a63f48fac 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -902,11 +902,18 @@ outblock.authenticating = FALSE; /* 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 @@ -1164,8 +1171,8 @@ if (tls_offered && !suppress_tls && { if (addr->transport_return == PENDING_DEFER) { - addr->cipher = tls_cipher; - addr->peerdn = tls_peerdn; + addr->cipher = tls_out.cipher; + addr->peerdn = tls_out.peerdn; } } } @@ -1181,7 +1188,7 @@ 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_active >= 0) +if (tls_out.active >= 0) { char *greeting_cmd; if (helo_data == NULL) @@ -1243,7 +1250,7 @@ we skip this. */ if (continue_hostname == NULL #ifdef SUPPORT_TLS - || tls_active >= 0 + || tls_out.active >= 0 #endif ) { @@ -2003,7 +2010,7 @@ if (completed_address && ok && send_quit) 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) && @@ -2052,9 +2059,9 @@ if (completed_address && ok && send_quit) 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 @@ -2104,7 +2111,7 @@ if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); END_OFF: #ifdef SUPPORT_TLS -tls_close(TRUE); +tls_close(FALSE, TRUE); #endif /* Close the socket, and return the appropriate value, first setting diff --git a/src/src/verify.c b/src/src/verify.c index ff18847e7..964bdf714 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -10,10 +10,14 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */ #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 */ @@ -150,6 +154,7 @@ do_callout(address_item *addr, host_item *host_list, transport_feedback *tf, 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; @@ -394,6 +399,13 @@ optimization. */ 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. */ @@ -405,7 +417,10 @@ for (host = host_list; host != NULL && !done; host = host->next) 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]; @@ -452,8 +467,9 @@ for (host = host_list; host != NULL && !done; host = host->next) 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); @@ -472,9 +488,14 @@ for (host = host_list; host != NULL && !done; host = host->next) 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 */ @@ -508,13 +529,173 @@ for (host = host_list; host != NULL && !done; host = host->next) 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. */ @@ -646,6 +827,7 @@ for (host = host_list; host != NULL && !done; host = host->next) { /*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 && @@ -739,29 +921,41 @@ for (host = host_list; host != NULL && !done; host = host->next) /* 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); } @@ -857,6 +1051,9 @@ return yield; +/* 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 ) { @@ -877,74 +1074,74 @@ return; } -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; } @@ -970,6 +1167,7 @@ inblock.buffersize = sizeof(inbuffer); 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(); @@ -991,20 +1189,12 @@ return responsebuffer[0]; 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'; @@ -1012,36 +1202,68 @@ 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 ) @@ -1049,26 +1271,32 @@ 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 * *************************************************/