From: Jeremy Harris Date: Thu, 21 Jun 2018 18:16:29 +0000 (+0100) Subject: TLS: rework client-side use with an explicit context rather than a global X-Git-Tag: exim-4.92-RC1~162 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/74f1a42304ce056cf979d22fb970faae161e3ab2 TLS: rework client-side use with an explicit context rather than a global --- diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 96508ff3f..db8804904 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -68,6 +68,11 @@ JH/13 For receent Openssl versions (1.1 onward) use modern generic protocol JH/14 Bug 2284: Fix DKIM signing for body lines starting with a pair of dots. +JH/15 Rework TLS client-side context management. Stop using a global, and + explicitly pass a context around. This enables future use of TLS for + connections to service-daemons (eg. malware scanning) while a client smtp + connection is using TLS; with cutthrough connections this is quite likely. + Exim version 4.91 ----------------- diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c index e0ed0244e..6ed57ef17 100644 --- a/src/exim_monitor/em_globals.c +++ b/src/exim_monitor/em_globals.c @@ -217,7 +217,7 @@ int string_datestamp_type = -1; BOOL timestamps_utc = FALSE; tls_support tls_in = { - -1, /* tls_active */ + {-1}, /* tls_active */ 0, /* bits */ FALSE, /* tls_certificate_verified */ #ifdef SUPPORT_DANE diff --git a/src/src/daemon.c b/src/src/daemon.c index 4a89afe74..a382a734e 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -648,7 +648,7 @@ if (pid == 0) the data structures if necessary. */ #ifdef SUPPORT_TLS - tls_close(TRUE, TLS_NO_SHUTDOWN); + tls_close(NULL, TLS_NO_SHUTDOWN); #endif /* Reset SIGHUP and SIGCHLD in the child in both cases. */ diff --git a/src/src/deliver.c b/src/src/deliver.c index c35f3fa5b..7127518e2 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -4478,7 +4478,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) treat it as if it is a continued connection (apart from the counter used for the log line mark). */ - if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) + if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) { DEBUG(D_deliver) debug_printf("lazy-callout-close: have conn still open from verification\n"); @@ -4985,12 +4985,13 @@ all pipes, so I do not see a reason to use non-blocking IO here release its TLS library context (if any) as responsibility was passed to the delivery child process. */ - if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) + if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) { #ifdef SUPPORT_TLS - tls_close(FALSE, TLS_NO_SHUTDOWN); + if (cutthrough.is_tls) + tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN); #endif - (void) close(cutthrough.fd); + (void) close(cutthrough.cctx.sock); release_cutthrough_connection(US"passed to transport proc"); } @@ -8528,9 +8529,9 @@ delivery_re_exec(int exec_type) { uschar * where; -if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) +if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) { - int pfd[2], channel_fd = cutthrough.fd, pid; + int pfd[2], channel_fd = cutthrough.cctx.sock, pid; smtp_peer_options = cutthrough.peer_options; continue_sequence = 0; @@ -8554,7 +8555,8 @@ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) { if (running_in_test_harness) millisleep(100); /* let parent debug out */ /* does not return */ - smtp_proxy_tls(big_buffer, big_buffer_size, pfd, 5*60); + smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size, + pfd, 5*60); } DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid); diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c index 0e9c3818c..db3db9180 100644 --- a/src/src/dkim_transport.c +++ b/src/src/dkim_transport.c @@ -56,7 +56,7 @@ DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd); to the socket. However only if we don't use TLS, as then there's another layer of indirection before the data finally hits the socket. */ -if (tls_out.active != out_fd) +if (tls_out.active.sock != out_fd) { ssize_t copied = 0; @@ -84,8 +84,8 @@ else while (sread) { #ifdef SUPPORT_TLS - wwritten = tls_out.active == out_fd - ? tls_write(FALSE, p, sread, FALSE) + wwritten = tls_out.active.sock == out_fd + ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE) : write(out_fd, CS p, sread); #else wwritten = write(out_fd, CS p, sread); diff --git a/src/src/exim.c b/src/src/exim.c index 58c2dc9af..2b4ecbc66 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -550,9 +550,9 @@ close_unwanted(void) { if (smtp_input) { - #ifdef SUPPORT_TLS - tls_close(TRUE, TLS_NO_SHUTDOWN); /* Shut down the TLS library */ - #endif +#ifdef SUPPORT_TLS + tls_close(NULL, TLS_NO_SHUTDOWN); /* Shut down the TLS library */ +#endif (void)close(fileno(smtp_in)); (void)close(fileno(smtp_out)); smtp_in = NULL; diff --git a/src/src/functions.h b/src/src/functions.h index e333f4e14..9b105774e 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -44,13 +44,13 @@ extern uschar * tls_cert_fprt_md5(void *); extern uschar * tls_cert_fprt_sha1(void *); extern uschar * tls_cert_fprt_sha256(void *); -extern int tls_client_start(int, host_item *, address_item *, +extern void * tls_client_start(int, host_item *, address_item *, transport_instance *, # ifdef SUPPORT_DANE dns_answer *, # endif - uschar **); -extern void tls_close(BOOL, int); + tls_support *, uschar **); +extern void tls_close(void *, int); extern BOOL tls_could_read(void); extern int tls_export_cert(uschar *, size_t, void *); extern int tls_feof(void); @@ -60,11 +60,11 @@ extern int tls_getc(unsigned); extern uschar *tls_getbuf(unsigned *); extern void tls_get_cache(void); extern int tls_import_cert(const uschar *, void **); -extern int tls_read(BOOL, uschar *, size_t); +extern int tls_read(void *, uschar *, size_t); extern int tls_server_start(const uschar *, uschar **); extern BOOL tls_smtp_buffered(void); extern int tls_ungetc(int); -extern int tls_write(BOOL, const uschar *, size_t, BOOL); +extern int tls_write(void *, const uschar *, size_t, BOOL); extern uschar *tls_validate_require_cipher(void); extern void tls_version_report(FILE *); # ifndef USE_GNUTLS @@ -271,7 +271,7 @@ extern int ip_connectedsocket(int, const uschar *, int, int, int, host_item *, uschar **, const blob *); extern int ip_get_address_family(int); extern void ip_keepalive(int, const uschar *, BOOL); -extern int ip_recv(int, uschar *, int, int); +extern int ip_recv(client_conn_ctx *, uschar *, int, int); extern int ip_socket(int, int); extern int ip_tcpsocket(const uschar *, uschar **, int); @@ -449,7 +449,7 @@ extern void smtp_get_cache(void); extern int smtp_handle_acl_fail(int, int, uschar *, uschar *); extern void smtp_log_no_mail(void); extern void smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL); -extern void smtp_proxy_tls(uschar *, size_t, int *, int); +extern void smtp_proxy_tls(void *, uschar *, size_t, int *, int); extern BOOL smtp_read_response(smtp_inblock *, uschar *, int, int, int); extern void smtp_reset(void *); extern void smtp_respond(uschar *, int, BOOL, uschar *); diff --git a/src/src/globals.c b/src/src/globals.c index 138a29e8a..3fa0e3e3f 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -98,10 +98,11 @@ BOOL move_frozen_messages = FALSE; /* These variables are outside the #ifdef because it keeps the code less cluttered in several places (e.g. during logging) if we can always refer to -them. Also, the tls_ variables are now always visible. */ +them. Also, the tls_ variables are now always visible. Note that these are +only used for smtp connections, not for service-daemon access. */ tls_support tls_in = { - .active = -1, + .active = {.sock = -1}, .bits = 0, .certificate_verified = FALSE, #ifdef SUPPORT_DANE @@ -118,7 +119,7 @@ tls_support tls_in = { .ocsp = OCSP_NOT_REQ }; tls_support tls_out = { - .active = -1, + .active = {.sock = -1}, .bits = 0, .certificate_verified = FALSE, #ifdef SUPPORT_DANE @@ -559,7 +560,7 @@ cut_t cutthrough = { .delivery = FALSE, /* when to attempt */ .defer_pass = FALSE, /* on defer: spool locally */ .is_tls = FALSE, /* not a TLS conn yet */ - .fd = -1, /* open connection */ + .cctx = {.sock = -1}, /* open connection */ .nrcpt = 0, /* number of addresses */ }; diff --git a/src/src/globals.h b/src/src/globals.h index f9be8b832..ef5b3a560 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -80,7 +80,7 @@ cluttered in several places (e.g. during logging) if we can always refer to them. Also, the tls_ variables are now always visible. */ typedef struct { - int active; /* fd/socket when in a TLS session */ + client_conn_ctx active; /* fd/socket when in a TLS session, and ptr to TLS context */ int bits; /* bits used in TLS session */ BOOL certificate_verified; /* Client certificate verified */ #ifdef SUPPORT_DANE @@ -314,7 +314,7 @@ typedef struct { unsigned delivery:1; /* When to attempt */ unsigned defer_pass:1; /* Pass 4xx to caller rather than spooling */ unsigned is_tls:1; /* Conn has TLS active */ - int fd; /* Open connection */ + client_conn_ctx cctx; /* Open connection */ int nrcpt; /* Count of addresses */ uschar * transport; /* Name of transport */ uschar * interface; /* (address of) */ diff --git a/src/src/ip.c b/src/src/ip.c index 2e8968528..555dc2d84 100644 --- a/src/src/ip.c +++ b/src/src/ip.c @@ -591,7 +591,7 @@ getting interrupted, and the possibility of select() returning with a positive result but no ready descriptor. Is this in fact possible? Arguments: - sock the socket + cctx the connection context (socket fd, possibly TLS context) buffer to read into bufsize the buffer size timeout the timeout @@ -601,24 +601,24 @@ Returns: > 0 => that much data read */ int -ip_recv(int sock, uschar *buffer, int buffsize, int timeout) +ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout) { int rc; -if (!fd_ready(sock, timeout)) +if (!fd_ready(cctx->sock, timeout)) return -1; /* The socket is ready, read from it (via TLS if it's active). On EOF (i.e. close down of the connection), set errno to zero; otherwise leave it alone. */ #ifdef SUPPORT_TLS -if (tls_out.active == sock) - rc = tls_read(FALSE, buffer, buffsize); -else if (tls_in.active == sock) - rc = tls_read(TRUE, buffer, buffsize); +if (cctx->tls_ctx) /* client TLS */ + rc = tls_read(cctx->tls_ctx, buffer, buffsize); +else if (tls_in.active.sock == cctx->sock) /* server TLS */ + rc = tls_read(NULL, buffer, buffsize); else #endif - rc = recv(sock, buffer, buffsize, 0); + rc = recv(cctx->sock, buffer, buffsize, 0); if (rc > 0) return rc; if (rc == 0) errno = 0; diff --git a/src/src/macros.h b/src/src/macros.h index 85ceb0acb..f22fe8c9a 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -85,7 +85,7 @@ as unsigned. */ a no-op once an SSL session is in progress. */ #ifdef SUPPORT_TLS -#define mac_smtp_fflush() if (tls_in.active < 0) fflush(smtp_out); +#define mac_smtp_fflush() if (tls_in.active.sock < 0) fflush(smtp_out); #else #define mac_smtp_fflush() fflush(smtp_out); #endif diff --git a/src/src/malware.c b/src/src/malware.c index a5de8a938..49456de4e 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -436,12 +436,13 @@ for (;;) static inline int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo) { +client_conn_ctx cctx = {.sock = sock}; int offset = 0; int i; do { - i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL)); + i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL)); if (i <= 0) { (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)"); @@ -576,7 +577,7 @@ const pcre *re; uschar * errstr; struct scan * scanent; const uschar * scanner_options; -int sock = -1; +client_conn_ctx malware_daemon_ctx = {.sock = -1}; time_t tmo; uschar * eml_filename, * eml_dir; @@ -657,12 +658,16 @@ if (!malware_ok) DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options); switch(scanent->conn) { - case MC_TCP: sock = ip_tcpsocket(scanner_options, &errstr, 5); break; - case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr); break; - case MC_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5); break; - default: /* compiler quietening */ break; + case MC_TCP: + malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5); break; + case MC_UNIX: + malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr); break; + case MC_STRM: + malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5); break; + default: + /* compiler quietening */ break; } - if (sock < 0) + if (malware_daemon_ctx.sock < 0) return m_panic_defer(scanent, CUS callout_address, errstr); break; } @@ -693,10 +698,10 @@ if (!malware_ok) scanner_name, scanrequest); /* send scan request */ - if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); - while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0) + while ((len = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) >= 0) if (len > 0) { if (Ustrstr(buf, US" 0 */ @@ -913,16 +918,16 @@ badseek: err = errno; int ovector[10*3]; /* read the size of report */ - if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo)) + if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo)) return m_panic_defer_3(scanent, CUS callout_address, - US"cannot read report size", sock); + US"cannot read report size", malware_daemon_ctx.sock); drweb_slen = ntohl(drweb_slen); tmpbuf = store_get(drweb_slen); /* read report body */ - if (!recv_len(sock, tmpbuf, drweb_slen, tmo)) + if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo)) return m_panic_defer_3(scanent, CUS callout_address, - US"cannot read report string", sock); + US"cannot read report string", malware_daemon_ctx.sock); tmpbuf[drweb_slen] = '\0'; /* try matcher on the line, grab substring */ @@ -961,7 +966,7 @@ badseek: err = errno; if (drweb_s) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s), - sock); + malware_daemon_ctx.sock); /* no virus found */ malware_name = NULL; @@ -978,13 +983,13 @@ badseek: err = errno; /* read aveserver's greeting and see if it is ready (2xx greeting) */ buf[0] = 0; - recv_line(sock, buf, sizeof(buf), tmo); + recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo); if (buf[0] != '2') /* aveserver is having problems */ return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unavailable (Responded: %s).", ((buf[0] != 0) ? buf : US "nothing") ), - sock); + malware_daemon_ctx.sock); /* prepare our command */ (void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n", @@ -993,13 +998,13 @@ badseek: err = errno; /* and send it */ DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n", scanner_name, buf); - if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0) + if (m_sock_send(malware_daemon_ctx.sock, buf, Ustrlen(buf), &errstr) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); malware_name = NULL; result = 0; /* read response lines, find malware name and final response */ - while (recv_line(sock, buf, sizeof(buf), tmo) > 0) + while (recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo) > 0) { if (buf[0] == '2') break; @@ -1018,22 +1023,22 @@ badseek: err = errno; } } - if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0) + if (m_sock_send(malware_daemon_ctx.sock, US"quit\r\n", 6, &errstr) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); /* read aveserver's greeting and see if it is ready (2xx greeting) */ buf[0] = 0; - recv_line(sock, buf, sizeof(buf), tmo); + recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo); if (buf[0] != '2') /* aveserver is having problems */ return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to quit dialogue (Responded: %s).", ((buf[0] != 0) ? buf : US "nothing") ), - sock); + malware_daemon_ctx.sock); if (result == DEFER) { - (void)close(sock); + (void)close(malware_daemon_ctx.sock); return DEFER; } break; @@ -1060,15 +1065,15 @@ badseek: err = errno; for (i = 0; i != nelem(cmdopt); i++) { - if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) + if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); + bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL)); if (bread > 0) av_buffer[bread]='\0'; if (bread < 0) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read answer %d (%s)", i, strerror(errno)), - sock); + malware_daemon_ctx.sock); for (j = 0; j < bread; j++) if (av_buffer[j] == '\r' || av_buffer[j] == '\n') av_buffer[j] ='@'; @@ -1077,7 +1082,7 @@ badseek: err = errno; /* pass the mailfile to fsecure */ file_name = string_sprintf("SCAN\t%s\n", eml_filename); - if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0) + if (m_sock_send(malware_daemon_ctx.sock, file_name, Ustrlen(file_name), &errstr) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); /* set up match */ @@ -1095,10 +1100,10 @@ badseek: err = errno; { errno = ETIMEDOUT; i = av_buffer+sizeof(av_buffer)-p; - if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0) + if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read result (%s)", strerror(errno)), - sock); + malware_daemon_ctx.sock); for (p[bread] = '\0'; (q = Ustrchr(p, '\n')); p = q+1) { @@ -1155,13 +1160,13 @@ badseek: err = errno; scanner_name, scanner_options); /* send scan request */ - if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) + if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); /* wait for result */ - if (!recv_len(sock, tmpbuf, 2, tmo)) + if (!recv_len(malware_daemon_ctx.sock, tmpbuf, 2, tmo)) return m_panic_defer_3(scanent, CUS callout_address, - US"unable to read 2 bytes from socket.", sock); + US"unable to read 2 bytes from socket.", malware_daemon_ctx.sock); /* get errorcode from one nibble */ kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F; @@ -1170,13 +1175,13 @@ badseek: err = errno; case 5: case 6: /* improper kavdaemon configuration */ return m_panic_defer_3(scanent, CUS callout_address, US"please reconfigure kavdaemon to NOT disinfect or remove infected files.", - sock); + malware_daemon_ctx.sock); case 1: return m_panic_defer_3(scanent, CUS callout_address, - US"reported 'scanning not completed' (code 1).", sock); + US"reported 'scanning not completed' (code 1).", malware_daemon_ctx.sock); case 7: return m_panic_defer_3(scanent, CUS callout_address, - US"reported 'kavdaemon damaged' (code 7).", sock); + US"reported 'kavdaemon damaged' (code 7).", malware_daemon_ctx.sock); } /* code 8 is not handled, since it is ambiguous. It appears mostly on @@ -1196,9 +1201,9 @@ badseek: err = errno; if (report_flag == 1) { /* read report size */ - if (!recv_len(sock, &kav_reportlen, 4, tmo)) + if (!recv_len(malware_daemon_ctx.sock, &kav_reportlen, 4, tmo)) return m_panic_defer_3(scanent, CUS callout_address, - US"cannot read report size", sock); + US"cannot read report size", malware_daemon_ctx.sock); /* it's possible that avp returns av_buffer[1] == 1 but the reportsize is 0 (!?) */ @@ -1221,7 +1226,7 @@ badseek: err = errno; /* coverity[tainted_data] */ while (kav_reportlen > 0) { - if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0) + if ((bread = recv_line(malware_daemon_ctx.sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0) break; kav_reportlen -= bread+1; @@ -1391,19 +1396,19 @@ badseek: err = errno; DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n", scanner_name, scanner_options); - if ( write(sock, file_name, Ustrlen(file_name)) < 0 - || write(sock, "\n", 1) != 1 + if ( write(malware_daemon_ctx.sock, file_name, Ustrlen(file_name)) < 0 + || write(malware_daemon_ctx.sock, "\n", 1) != 1 ) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to write to UNIX socket (%s)", scanner_options), - sock); + malware_daemon_ctx.sock); /* wait for result */ memset(av_buffer, 0, sizeof(av_buffer)); - if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0) + if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read from UNIX socket (%s)", scanner_options), - sock); + malware_daemon_ctx.sock); /* infected ? */ if (av_buffer[0] == '1') { @@ -1414,7 +1419,7 @@ badseek: err = errno; } else if (!strncmp(CS av_buffer, "-1", 2)) return m_panic_defer_3(scanent, CUS callout_address, - US"scanner reported error", sock); + US"scanner reported error", malware_daemon_ctx.sock); else /* all ok, no virus */ malware_name = NULL; @@ -1573,7 +1578,7 @@ badseek: err = errno; * on both connections (as one host could resolve to multiple ips) */ for (;;) { - if ((sock = m_tcpsocket(cd->hostspec, cd->tcp_port, + if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr, &cmd_str)) >= 0) { /* Connection successfully established with a server */ @@ -1584,7 +1589,7 @@ badseek: err = errno; if (cd->retry <= 0) break; while (cd->retry > 0) cd->retry = sleep(cd->retry); } - if (sock >= 0) + if (malware_daemon_ctx.sock >= 0) break; (void) m_panic_defer(scanent, CUS callout_address, errstr); @@ -1601,7 +1606,7 @@ badseek: err = errno; else for (;;) { - if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0) + if ((malware_daemon_ctx.sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0) { hostname = cv[0]->hostspec; break; @@ -1628,11 +1633,11 @@ badseek: err = errno; /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */ if (cmd_str.len) - if (send(sock, cmd_str.data, cmd_str.len, 0) < 0) + if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0) return m_panic_defer_3(scanent, CUS hostname, string_sprintf("unable to send zINSTREAM to socket (%s)", strerror(errno)), - sock); + malware_daemon_ctx.sock); /* calc file size */ if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0) @@ -1641,7 +1646,7 @@ badseek: err = errno; return m_panic_defer_3(scanent, NULL, string_sprintf("can't open spool file %s: %s", eml_filename, strerror(err)), - sock); + malware_daemon_ctx.sock); } if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0) { @@ -1651,7 +1656,7 @@ b_seek: err = errno; return m_panic_defer_3(scanent, NULL, string_sprintf("can't seek spool file %s: %s", eml_filename, strerror(err)), - sock); + malware_daemon_ctx.sock); } fsize_uint = (unsigned int) fsize; if ((off_t)fsize_uint != fsize) @@ -1660,7 +1665,7 @@ b_seek: err = errno; return m_panic_defer_3(scanent, NULL, string_sprintf("seeking spool file %s, size overflow", eml_filename), - sock); + malware_daemon_ctx.sock); } if (lseek(clam_fd, 0, SEEK_SET) < 0) goto b_seek; @@ -1671,7 +1676,7 @@ b_seek: err = errno; return m_panic_defer_3(scanent, NULL, string_sprintf("unable to allocate memory %u for file (%s)", fsize_uint, eml_filename), - sock); + malware_daemon_ctx.sock); } if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0) @@ -1681,21 +1686,21 @@ b_seek: err = errno; return m_panic_defer_3(scanent, NULL, string_sprintf("can't read spool file %s: %s", eml_filename, strerror(err)), - sock); + malware_daemon_ctx.sock); } (void)close(clam_fd); /* send file body to socket */ send_size = htonl(fsize_uint); send_final_zeroblock = 0; - if ((send(sock, &send_size, sizeof(send_size), 0) < 0) || - (send(sock, clamav_fbuf, fsize_uint, 0) < 0) || - (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) + if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) || + (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) || + (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0)) { free(clamav_fbuf); return m_panic_defer_3(scanent, NULL, string_sprintf("unable to send file body to socket (%s)", hostname), - sock); + malware_daemon_ctx.sock); } free(clamav_fbuf); @@ -1722,10 +1727,10 @@ b_seek: err = errno; scanner_name, scanner_options); if (cmd_str.len) - if (send(sock, cmd_str.data, cmd_str.len, 0) < 0) + if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to write to socket (%s)", strerror(errno)), - sock); + malware_daemon_ctx.sock); /* Do not shut down the socket for writing; a user report noted that * clamd 0.70 does not react well to this. */ @@ -1735,9 +1740,10 @@ b_seek: err = errno; /* Read the result */ memset(av_buffer, 0, sizeof(av_buffer)); - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); - (void)close(sock); - sock = -1; + bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL)); + (void)close(malware_daemon_ctx.sock); + malware_daemon_ctx.sock = -1; + malware_daemon_ctx.tls_ctx = NULL; if (bread <= 0) return m_panic_defer(scanent, CUS callout_address, @@ -1863,7 +1869,7 @@ b_seek: err = errno; if (s++) if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%')) return m_panic_defer_3(scanent, NULL, - US"unsafe sock scanner call spec", sock); + US"unsafe sock scanner call spec", malware_daemon_ctx.sock); } else sockline_scanner = sockline_scanner_default; @@ -1874,13 +1880,13 @@ b_seek: err = errno; sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing trigger specification", &errstr); if (!sockline_trig_re) - return m_panic_defer_3(scanent, NULL, errstr, sock); + return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock); /* find virus name regex */ sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep, "missing virus name regex specification", &errstr); if (!sockline_name_re) - return m_panic_defer_3(scanent, NULL, errstr, sock); + return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock); /* prepare scanner call - security depends on expansions check above */ commandline = string_sprintf( CS sockline_scanner, CS eml_filename); @@ -1888,20 +1894,20 @@ b_seek: err = errno; string_printing(commandline)); /* Pass the command string to the socket */ - if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0) + if (m_sock_send(malware_daemon_ctx.sock, commandline, Ustrlen(commandline), &errstr) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); /* Read the result */ - bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL)); + bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL)); if (bread <= 0) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to read from socket (%s)", strerror(errno)), - sock); + malware_daemon_ctx.sock); if (bread == sizeof(av_buffer)) return m_panic_defer_3(scanent, CUS callout_address, - US"buffer too small", sock); + US"buffer too small", malware_daemon_ctx.sock); av_buffer[bread] = '\0'; linebuffer = string_copy(av_buffer); DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ", @@ -1940,16 +1946,16 @@ b_seek: err = errno; string_sprintf("invalid option '%s'", scanner_options)); } - if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) + if((malware_daemon_ctx.sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0) return m_panic_defer(scanent, CUS callout_address, errstr); malware_name = NULL; DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan\n", scanner_name); - if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK) + if ((retval = mksd_scan_packed(scanent, malware_daemon_ctx.sock, eml_filename, tmo)) != OK) { - close (sock); + close (malware_daemon_ctx.sock); return retval; } break; @@ -2018,7 +2024,7 @@ b_seek: err = errno; /* wait for result */ for (avast_stage = AVA_HELO; - (nread = recv_line(sock, buf, sizeof(buf), tmo)) > 0; + (nread = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) > 0; ) { int slen = Ustrlen(buf); @@ -2072,12 +2078,12 @@ b_seek: err = errno; /* send config-cmd or scan-request to socket */ len = Ustrlen(scanrequest); - if (send(sock, scanrequest, len, 0) == -1) + if (send(malware_daemon_ctx.sock, scanrequest, len, 0) == -1) { scanrequest[len-1] = '\0'; return m_panic_defer_3(scanent, CUS callout_address, string_sprintf( "unable to send request '%s' to socket (%s): %s", - scanrequest, scanner_options, strerror(errno)), sock); + scanrequest, scanner_options, strerror(errno)), malware_daemon_ctx.sock); } break; } @@ -2136,20 +2142,20 @@ b_seek: err = errno; else if (buf[0] != '2') error_message = buf; DEBUG(D_acl) debug_printf_indent("sent to avast QUIT\n"); - if (send(sock, "QUIT\n", 5, 0) == -1) + if (send(malware_daemon_ctx.sock, "QUIT\n", 5, 0) == -1) return m_panic_defer_3(scanent, CUS callout_address, string_sprintf("unable to send quit request to socket (%s): %s", - scanner_options, strerror(errno)), sock); + scanner_options, strerror(errno)), malware_daemon_ctx.sock); if (error_message) - return m_panic_defer_3(scanent, CUS callout_address, error_message, sock); + return m_panic_defer_3(scanent, CUS callout_address, error_message, malware_daemon_ctx.sock); } #endif } /* scanner type switch */ - if (sock >= 0) - (void) close (sock); + if (malware_daemon_ctx.sock >= 0) + (void) close (malware_daemon_ctx.sock); malware_ok = TRUE; /* set "been here, done that" marker */ } diff --git a/src/src/receive.c b/src/src/receive.c index c3781420f..974756f06 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1181,7 +1181,7 @@ switch(where) case ACL_WHERE_DKIM: case ACL_WHERE_MIME: case ACL_WHERE_DATA: - if ( cutthrough.fd >= 0 && cutthrough.delivery + if ( cutthrough.cctx.sock >= 0 && cutthrough.delivery && (acl_removed_headers || acl_added_headers)) { log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs" @@ -2995,7 +2995,7 @@ 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. Having created it, send the headers to the destination. */ -if (cutthrough.fd >= 0 && cutthrough.delivery) +if (cutthrough.cctx.sock >= 0 && cutthrough.delivery) { if (received_count > received_headers_max) { @@ -4179,7 +4179,7 @@ for this message. */ XXX We do not handle queue-only, freezing, or blackholes. */ -if(cutthrough.fd >= 0 && cutthrough.delivery) +if(cutthrough.cctx.sock >= 0 && cutthrough.delivery) { uschar * msg = cutthrough_finaldot(); /* Ask the target system to accept the message */ /* Logging was done in finaldot() */ diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c index e5e25ae4b..bc58a1f82 100644 --- a/src/src/routers/iplookup.c +++ b/src/src/routers/iplookup.c @@ -230,7 +230,8 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer, for (h = host; h; h = h->next) { - int host_af, query_socket; + int host_af; + client_conn_ctx query_cctx = {0}; /* Skip any hosts for which we have no address */ @@ -241,9 +242,9 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer, host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET; - query_socket = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM, + query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM, host_af); - if (query_socket < 0) + if (query_cctx.sock < 0) { if (ob->optional) return PASS; addr->message = string_sprintf("failed to create socket in %s router", @@ -256,10 +257,10 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer, router will timeout later on the read call). */ /*XXX could take advantage of TFO */ - if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout, + if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout, ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0) { - close(query_socket); + close(query_cctx.sock); DEBUG(D_route) debug_printf("connection to %s failed: %s\n", h->address, strerror(errno)); @@ -268,18 +269,18 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer, /* Send the query. If it fails, just continue with the next address. */ - if (send(query_socket, query, query_len, 0) < 0) + if (send(query_cctx.sock, query, query_len, 0) < 0) { DEBUG(D_route) debug_printf("send to %s failed\n", h->address); - (void)close(query_socket); + (void)close(query_cctx.sock); continue; } /* Read the response and close the socket. If the read fails, try the next IP address. */ - count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout); - (void)close(query_socket); + count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, ob->timeout); + (void)close(query_cctx.sock); if (count <= 0) { DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)? diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index e09d35cba..0afb97ca6 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -340,7 +340,7 @@ fd_set fds; struct timeval tzero; #ifdef SUPPORT_TLS -if (tls_in.active >= 0) +if (tls_in.active.sock >= 0) return !tls_could_read(); #endif @@ -429,7 +429,7 @@ smtp_command_timeout_exit(void) { log_write(L_lost_incoming_connection, LOG_MAIN, "SMTP command timeout on%s connection from %s", - tls_in.active >= 0 ? " TLS" : "", host_and_ident(FALSE)); + tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE)); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */ smtp_notquit_exit(US"command-timeout", US"421", @@ -922,9 +922,9 @@ if (rcpt_in_progress) /* Now write the string */ #ifdef SUPPORT_TLS -if (tls_in.active >= 0) +if (tls_in.active.sock >= 0) { - if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer), more) < 0) + if (tls_write(NULL, big_buffer, Ustrlen(big_buffer), more) < 0) smtp_write_error = -1; } else @@ -951,7 +951,7 @@ Returns: 0 for no error; -1 after an error int smtp_fflush(void) { -if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; +if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; return smtp_write_error; } @@ -3686,7 +3686,7 @@ switch(rc) received_protocol = (sender_host_address ? protocols : protocols_local) - [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)]; + [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)]; *s = *ss = US"235 Authentication succeeded"; authenticated_by = au; break; @@ -3780,7 +3780,7 @@ else smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); #ifdef SUPPORT_TLS -tls_close(TRUE, TLS_SHUTDOWN_NOWAIT); +tls_close(NULL, TLS_SHUTDOWN_NOWAIT); #endif log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", @@ -3891,7 +3891,7 @@ while (done <= 0) #ifdef AUTH_TLS /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */ - if ( tls_in.active >= 0 + if ( tls_in.active.sock >= 0 && tls_in.peercert && tls_in.certificate_verified && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd @@ -4108,7 +4108,7 @@ while (done <= 0) host_build_sender_fullhost(); /* Rebuild */ set_process_info("handling%s incoming connection from %s", - (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE)); + (tls_in.active.sock >= 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, @@ -4364,7 +4364,7 @@ while (done <= 0) secure connection. */ #ifdef SUPPORT_TLS - if (tls_in.active < 0 && + if (tls_in.active.sock < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) { g = string_catn(g, smtp_code, 3); @@ -4402,7 +4402,7 @@ while (done <= 0) has been seen. */ #ifdef SUPPORT_TLS - if (tls_in.active >= 0) (void)tls_write(TRUE, g->s, g->ptr, FALSE); else + if (tls_in.active.sock >= 0) (void)tls_write(NULL, g->s, g->ptr, FALSE); else #endif { @@ -4425,7 +4425,7 @@ while (done <= 0) [ (esmtp ? pextend + (sender_host_authenticated ? pauthed : 0) : pnormal) - + (tls_in.active >= 0 ? pcrpted : 0) + + (tls_in.active.sock >= 0 ? pcrpted : 0) ]; cancel_cutthrough_connection(TRUE, US"sent EHLO response"); smtp_reset(reset_point); @@ -5213,7 +5213,7 @@ while (done <= 0) ACL may have delayed. To handle cutthrough delivery enforce a dummy call to get the DATA command sent. */ - if (acl_smtp_predata == NULL && cutthrough.fd < 0) + if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0) rc = OK; else { @@ -5367,7 +5367,7 @@ while (done <= 0) { DEBUG(D_any) debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n"); - if (tls_in.active < 0) + if (tls_in.active.sock < 0) smtp_inend = smtp_inptr = smtp_inbuffer; /* and if TLS is already active, tls_server_start() should fail */ } @@ -5410,7 +5410,7 @@ while (done <= 0) [ (esmtp ? pextend + (sender_host_authenticated ? pauthed : 0) : pnormal) - + (tls_in.active >= 0 ? pcrpted : 0) + + (tls_in.active.sock >= 0 ? pcrpted : 0) ]; sender_host_auth_pubname = sender_host_authenticated = NULL; @@ -5470,7 +5470,7 @@ while (done <= 0) smtp_printf("554 Security failure\r\n", FALSE); break; } - tls_close(TRUE, TLS_SHUTDOWN_NOWAIT); + tls_close(NULL, TLS_SHUTDOWN_NOWAIT); break; #endif @@ -5512,7 +5512,7 @@ while (done <= 0) buffer[0] = 0; Ustrcat(buffer, " AUTH"); #ifdef SUPPORT_TLS - if (tls_in.active < 0 && + if (tls_in.active.sock < 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 c968f70e2..70aaef3e3 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -411,11 +411,11 @@ HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n, more ? " (more expected)" : ""); #ifdef SUPPORT_TLS -if (tls_out.active == outblock->sock) - rc = tls_write(FALSE, outblock->buffer, n, more); +if (outblock->cctx->tls_ctx) + rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more); else #endif - rc = send(outblock->sock, outblock->buffer, n, + rc = send(outblock->cctx->sock, outblock->buffer, n, #ifdef MSG_MORE more ? MSG_MORE : 0 #else @@ -546,7 +546,7 @@ read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout) uschar *p = buffer; uschar *ptr = inblock->ptr; uschar *ptrend = inblock->ptrend; -int sock = inblock->sock; +client_conn_ctx * cctx = inblock->cctx; /* Loop for reading multiple packets or reading another packet after emptying a previously-read one. */ @@ -584,7 +584,7 @@ for (;;) /* Need to read a new input packet. */ - if((rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout)) <= 0) + if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0) { DEBUG(D_deliver|D_transport|D_acl) debug_printf_indent(errno ? " SMTP(%s)<<\n" : " SMTP(closed)<<\n", diff --git a/src/src/spam.c b/src/src/spam.c index 8e08a40b7..9384bfa0d 100644 --- a/src/src/spam.c +++ b/src/src/spam.c @@ -193,7 +193,7 @@ uschar *user_name; uschar user_name_buffer[128]; unsigned long mbox_size; FILE *mbox_file; -int spamd_sock = -1; +client_conn_ctx spamd_cctx = {.sock = -1}; uschar spamd_buffer[32600]; int i, j, offset, result; uschar spamd_version[8]; @@ -344,14 +344,14 @@ start = time(NULL); for (;;) { /*XXX could potentially use TFO early-data here */ - if ( (spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0 + if ( (spamd_cctx.sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0 || sd->retry <= 0 ) break; DEBUG(D_acl) debug_printf_indent("spamd: server %s: retry conn\n", sd->hostspec); while (sd->retry > 0) sd->retry = sleep(sd->retry); } - if (spamd_sock >= 0) + if (spamd_cctx.sock >= 0) break; log_write(0, LOG_MAIN, "%s spamd: %s", loglabel, errstr); @@ -367,8 +367,8 @@ start = time(NULL); } } -(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK); -/* now we are connected to spamd on spamd_sock */ +(void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK); +/* now we are connected to spamd on spamd_cctx.sock */ if (sd->is_rspamd) { gstring * req_str; @@ -392,7 +392,7 @@ if (sd->is_rspamd) if ((s = expand_string(US"$authenticated_id")) && *s) req_str = string_append(req_str, 3, "User: ", s, "\r\n"); req_str = string_catn(req_str, US"\r\n", 2); - wrote = send(spamd_sock, req_str->s, req_str->ptr, 0); + wrote = send(spamd_cctx.sock, req_str->s, req_str->ptr, 0); } else { /* spamassassin variant */ @@ -402,12 +402,12 @@ else user_name, mbox_size); /* send our request */ - wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0); + wrote = send(spamd_cctx.sock, spamd_buffer, Ustrlen(spamd_buffer), 0); } if (wrote == -1) { - (void)close(spamd_sock); + (void)close(spamd_cctx.sock); log_write(0, LOG_MAIN|LOG_PANIC, "%s spamd %s send failed: %s", loglabel, callout_address, strerror(errno)); goto defer; @@ -424,10 +424,10 @@ if (wrote == -1) * broken in more recent versions (up to 10.4). */ #ifndef NO_POLL_H -pollfd.fd = spamd_sock; +pollfd.fd = spamd_cctx.sock; pollfd.events = POLLOUT; #endif -(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK); +(void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK); do { read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file); @@ -443,8 +443,8 @@ again: select_tv.tv_sec = 1; select_tv.tv_usec = 0; FD_ZERO(&select_fd); - FD_SET(spamd_sock, &select_fd); - result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv); + FD_SET(spamd_cctx.sock, &select_fd); + result = select(spamd_cctx.sock+1, NULL, &select_fd, NULL, &select_tv); #endif /* End Erik's patch */ @@ -462,16 +462,16 @@ again: log_write(0, LOG_MAIN|LOG_PANIC, "%s timed out writing spamd %s, socket", loglabel, callout_address); } - (void)close(spamd_sock); + (void)close(spamd_cctx.sock); goto defer; } - wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0); + wrote = send(spamd_cctx.sock,spamd_buffer + offset,read - offset,0); if (wrote == -1) { log_write(0, LOG_MAIN|LOG_PANIC, "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno)); - (void)close(spamd_sock); + (void)close(spamd_cctx.sock); goto defer; } if (offset + wrote != read) @@ -487,7 +487,7 @@ if (ferror(mbox_file)) { log_write(0, LOG_MAIN|LOG_PANIC, "%s error reading spool file: %s", loglabel, strerror(errno)); - (void)close(spamd_sock); + (void)close(spamd_cctx.sock); goto defer; } @@ -495,12 +495,12 @@ if (ferror(mbox_file)) /* we're done sending, close socket for writing */ if (!sd->is_rspamd) - shutdown(spamd_sock,SHUT_WR); + shutdown(spamd_cctx.sock,SHUT_WR); /* read spamd response using what's left of the timeout. */ memset(spamd_buffer, 0, sizeof(spamd_buffer)); offset = 0; -while ((i = ip_recv(spamd_sock, +while ((i = ip_recv(&spamd_cctx, spamd_buffer + offset, sizeof(spamd_buffer) - offset - 1, sd->timeout - time(NULL) + start)) > 0) @@ -512,12 +512,12 @@ if (i <= 0 && errno != 0) { log_write(0, LOG_MAIN|LOG_PANIC, "%s error reading from spamd %s, socket: %s", loglabel, callout_address, strerror(errno)); - (void)close(spamd_sock); + (void)close(spamd_cctx.sock); return DEFER; } /* reading done */ -(void)close(spamd_sock); +(void)close(spamd_cctx.sock); if (sd->is_rspamd) { /* rspamd variant of reply */ diff --git a/src/src/structs.h b/src/src/structs.h index 8384920ee..9ee3dba8d 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -780,15 +780,21 @@ md5; typedef struct sha1 { unsigned int H[5]; unsigned int length; - } -sha1; +} sha1; + +/* A client-initiated connection. If TLS, the second element is non-NULL */ +typedef struct { + int sock; + void * tls_ctx; +} client_conn_ctx; + /* Structure used to hold incoming packets of SMTP responses for a specific socket. The packets which may contain multiple lines (and in some cases, multiple responses). */ typedef struct smtp_inblock { - int sock; /* the socket */ + client_conn_ctx * cctx; /* the connection */ int buffersize; /* the size of the buffer */ uschar *ptr; /* current position in the buffer */ uschar *ptrend; /* end of data in the buffer */ @@ -800,7 +806,7 @@ specific socket. The packets which may contain multiple lines when pipelining is in use. */ typedef struct smtp_outblock { - int sock; /* the socket */ + client_conn_ctx * cctx; /* the connection */ int cmd_count; /* count of buffered commands */ int buffersize; /* the size of the buffer */ BOOL authenticating; /* TRUE when authenticating */ diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 08c1d939e..12c9fdb38 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -213,7 +213,7 @@ second connection. XXX But see gnutls_session_get_ptr() */ -static exim_gnutls_state_st state_server, state_client; +static exim_gnutls_state_st state_server; /* 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 @@ -448,7 +448,8 @@ gnutls_datum_t channel; #endif tls_support * tlsp = state->tlsp; -tlsp->active = state->fd_out; +tlsp->active.sock = state->fd_out; +tlsp->active.tls_ctx = state; cipher = gnutls_cipher_get(state->session); /* returns size in "bytes" */ @@ -1262,6 +1263,7 @@ tls_init( const uschar *crl, const uschar *require_ciphers, exim_gnutls_state_st **caller_state, + tls_support * tlsp, uschar ** errstr) { exim_gnutls_state_st *state; @@ -1310,9 +1312,15 @@ if (!exim_gnutls_base_init_done) if (host) { - state = &state_client; + /* For client-side sessions we allocate a context. This lets us run + several in parallel. */ + int old_pool = store_pool; + store_pool = POOL_PERM; + state = store_get(sizeof(exim_gnutls_state_st)); + store_pool = old_pool; + memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); - state->tlsp = &tls_out; + state->tlsp = tlsp; DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n"); rc = gnutls_init(&state->session, GNUTLS_CLIENT); } @@ -1320,7 +1328,7 @@ else { state = &state_server; memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); - state->tlsp = &tls_in; + state->tlsp = tlsp; DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n"); rc = gnutls_init(&state->session, GNUTLS_SERVER); } @@ -1980,7 +1988,7 @@ int rc; exim_gnutls_state_st * state = NULL; /* Check for previous activation */ -if (tls_in.active >= 0) +if (tls_in.active.sock >= 0) { tls_error(US"STARTTLS received after TLS started", "", NULL, errstr); smtp_printf("554 Already in TLS\r\n", FALSE); @@ -1994,7 +2002,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); if ((rc = tls_init(NULL, tls_certificate, tls_privatekey, NULL, tls_verify_certificates, tls_crl, - require_ciphers, &state, errstr)) != OK) return rc; + require_ciphers, &state, &tls_in, errstr)) != OK) return rc; /* If this is a host for which certificate verification is mandatory or optional, set up appropriately. */ @@ -2241,26 +2249,27 @@ Arguments: Which implies cert must be requested and supplied, dane verify must pass, and cert verify irrelevant (incl. hostnames), and (caller handled) require_tls + tlsp record details of channel configuration errstr error string pointer -Returns: OK/DEFER/FAIL (because using common functions), - but for a client, DEFER and FAIL have the same meaning +Returns: Pointer to TLS session context, or NULL on error */ -int +void * tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, transport_instance * tb, #ifdef SUPPORT_DANE dns_answer * tlsa_dnsa, #endif - uschar ** errstr) + tls_support * tlsp, uschar ** errstr) { smtp_transport_options_block *ob = (smtp_transport_options_block *)tb->options_block; int rc; exim_gnutls_state_st * state = NULL; uschar *cipher_list = NULL; + #ifndef DISABLE_OCSP BOOL require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK; @@ -2276,7 +2285,7 @@ if (tlsa_dnsa && ob->dane_require_tls_ciphers) /* not using expand_check_tlsvar because not yet in state */ if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers", &cipher_list, errstr)) - return DEFER; + return NULL; cipher_list = cipher_list && *cipher_list ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers; } @@ -2285,10 +2294,10 @@ if (tlsa_dnsa && ob->dane_require_tls_ciphers) if (!cipher_list) cipher_list = ob->tls_require_ciphers; -if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, +if (tls_init(host, ob->tls_certificate, ob->tls_privatekey, ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, - cipher_list, &state, errstr)) != OK) - return rc; + cipher_list, &state, tlsp, errstr) != OK) + return NULL; { int dh_min_bits = ob->tls_dh_min_bits; @@ -2357,9 +2366,11 @@ if (request_ocsp) DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n"); if ((rc = gnutls_ocsp_status_request_enable_client(state->session, NULL, 0, NULL)) != OK) - return tls_error(US"cert-status-req", - gnutls_strerror(rc), state->host, errstr); - tls_out.ocsp = OCSP_NOT_RESP; + { + tls_error(US"cert-status-req", gnutls_strerror(rc), state->host, errstr); + return NULL; + } + tlsp->ocsp = OCSP_NOT_RESP; } #endif @@ -2387,20 +2398,26 @@ while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); alarm(0); if (rc != GNUTLS_E_SUCCESS) + { if (sigalrm_seen) { gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED); - return tls_error(US"gnutls_handshake", "timed out", state->host, errstr); + tls_error(US"gnutls_handshake", "timed out", state->host, errstr); } else - return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr); + tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr); + return NULL; + } DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n"); /* Verify late */ if (!verify_certificate(state, errstr)) - return tls_error(US"certificate verification failed", *errstr, state->host, errstr); + { + tls_error(US"certificate verification failed", *errstr, state->host, errstr); + return NULL; + } #ifndef DISABLE_OCSP if (require_ocsp) @@ -2425,24 +2442,25 @@ if (require_ocsp) if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0) { - tls_out.ocsp = OCSP_FAILED; - return tls_error(US"certificate status check failed", NULL, state->host, errstr); + tlsp->ocsp = OCSP_FAILED; + tls_error(US"certificate status check failed", NULL, state->host, errstr); + return NULL; } DEBUG(D_tls) debug_printf("Passed OCSP checking\n"); - tls_out.ocsp = OCSP_VFIED; + tlsp->ocsp = OCSP_VFIED; } #endif /* Figure out peer DN, and if authenticated, etc. */ -if ((rc = peer_status(state, errstr)) != OK) - return rc; +if (peer_status(state, errstr) != OK) + return NULL; /* Sets various Exim expansion variables; may need to adjust for ACL callouts */ extract_exim_vars_from_tls_state(state); -return OK; +return state; } @@ -2457,6 +2475,7 @@ daemon, to shut down the TLS library, without actually doing a shutdown (which would tamper with the TLS session in the parent process). Arguments: + ct_ctx client context pointer, or NULL for the one global server context shutdown 1 if TLS close-alert is to be sent, 2 if also response to be waited for @@ -2464,11 +2483,11 @@ Returns: nothing */ void -tls_close(BOOL is_server, int shutdown) +tls_close(void * ct_ctx, int shutdown) { -exim_gnutls_state_st *state = is_server ? &state_server : &state_client; +exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; -if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */ +if (!state->tlsp || state->tlsp->active.sock < 0) return; /* TLS was not active */ if (shutdown) { @@ -2484,15 +2503,10 @@ gnutls_deinit(state->session); gnutls_certificate_free_credentials(state->x509_cred); -state->tlsp->active = -1; +state->tlsp->active.sock = -1; +state->tlsp->active.tls_ctx = NULL; if (state->xfer_buffer) store_free(state->xfer_buffer); memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); - -if (!state_server.session && !state_client.session) - { - gnutls_global_deinit(); - exim_gnutls_base_init_done = FALSE; - } } @@ -2549,7 +2563,8 @@ else if (inbytes == 0) gnutls_certificate_free_credentials(state->x509_cred); state->session = NULL; - state->tlsp->active = -1; + state->tlsp->active.sock = -1; + state->tlsp->active.tls_ctx = NULL; state->tlsp->bits = 0; state->tlsp->certificate_verified = FALSE; tls_channelbinding_b64 = NULL; @@ -2658,6 +2673,7 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm then the caller must feed DKIM. Arguments: + ct_ctx client context pointer, or NULL for the one global server context buff buffer of data len size of buffer @@ -2666,9 +2682,9 @@ Returns: the number of bytes read */ int -tls_read(BOOL is_server, uschar *buff, size_t len) +tls_read(void * ct_ctx, uschar *buff, size_t len) { -exim_gnutls_state_st *state = is_server ? &state_server : &state_client; +exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; ssize_t inbytes; if (len > INT_MAX) @@ -2704,7 +2720,7 @@ return -1; /* Arguments: - is_server channel specifier + ct_ctx client context pointer, or NULL for the one global server context buff buffer of data len number of bytes more more data expected soon @@ -2714,11 +2730,11 @@ Returns: the number of bytes after a successful write, */ int -tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more) +tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more) { ssize_t outbytes; size_t left = len; -exim_gnutls_state_st *state = is_server ? &state_server : &state_client; +exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; #ifdef SUPPORT_CORK static BOOL corked = FALSE; diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index e7bba0221..adabc963e 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -117,7 +117,9 @@ static const uschar *sid_ctx = US"exim"; Simple case: client, `client_ctx` As a client, we can be doing a callout or cut-through delivery while receiving a message. So we have a client context, which should have options initialised - from the SMTP Transport. + from the SMTP Transport. We may also concurrently want to make TLS connections + to utility daemons, so client-contexts are allocated and passed around in call + args rather than using a gobal. Server: There are two cases: with and without ServerNameIndication from the client. @@ -131,9 +133,12 @@ Server: configuration. */ -static SSL_CTX *client_ctx = NULL; +typedef struct { + SSL_CTX * ctx; + SSL * ssl; +} exim_openssl_client_tls_ctx; + static SSL_CTX *server_ctx = NULL; -static SSL *client_ssl = NULL; static SSL *server_ssl = NULL; #ifdef EXIM_HAVE_OPENSSL_TLSEXT @@ -1975,7 +1980,7 @@ static uschar cipherbuf[256]; /* Check for previous activation */ -if (tls_in.active >= 0) +if (tls_in.active.sock >= 0) { tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr); smtp_printf("554 Already in TLS\r\n", FALSE); @@ -2130,7 +2135,8 @@ receive_feof = tls_feof; receive_ferror = tls_ferror; receive_smtp_buffered = tls_smtp_buffered; -tls_in.active = fileno(smtp_out); +tls_in.active.sock = fileno(smtp_out); +tls_in.active.tls_ctx = NULL; /* not using explicit ctx for server-side */ return OK; } @@ -2249,23 +2255,23 @@ Argument: addr the first address tb transport (always smtp) tlsa_dnsa tlsa lookup, if DANE, else null + tlsp record details of channel configuration errstr error string pointer -Returns: OK on success - FAIL otherwise - note that tls_error() will not give DEFER - because this is not a server +Returns: Pointer to TLS session context, or NULL on error */ -int +void * tls_client_start(int fd, host_item *host, address_item *addr, transport_instance * tb, #ifdef SUPPORT_DANE dns_answer * tlsa_dnsa, #endif - uschar ** errstr) + tls_support * tlsp, uschar ** errstr) { smtp_transport_options_block * ob = (smtp_transport_options_block *)tb->options_block; +exim_openssl_client_tls_ctx * exim_client_ctx; static uschar peerdn[256]; uschar * expciphers; int rc; @@ -2276,8 +2282,13 @@ BOOL request_ocsp = FALSE; BOOL require_ocsp = FALSE; #endif +rc = store_pool; +store_pool = POOL_PERM; +exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx)); +store_pool = rc; + #ifdef SUPPORT_DANE -tls_out.tlsa_usage = 0; +tlsp->tlsa_usage = 0; #endif #ifndef DISABLE_OCSP @@ -2308,15 +2319,15 @@ tls_out.tlsa_usage = 0; } #endif -rc = tls_init(&client_ctx, host, NULL, +rc = tls_init(&exim_client_ctx->ctx, host, NULL, ob->tls_certificate, ob->tls_privatekey, #ifndef DISABLE_OCSP (void *)(long)request_ocsp, #endif addr, &client_static_cbinfo, errstr); -if (rc != OK) return rc; +if (rc != OK) return NULL; -tls_out.certificate_verified = FALSE; +tlsp->certificate_verified = FALSE; client_verify_callback_called = FALSE; expciphers = NULL; @@ -2328,7 +2339,7 @@ if (tlsa_dnsa) if (ob->dane_require_tls_ciphers && !expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers", &expciphers, errstr)) - return FAIL; + return NULL; if (expciphers && *expciphers == '\0') expciphers = NULL; } @@ -2336,7 +2347,7 @@ if (tlsa_dnsa) if (!expciphers && !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) - return FAIL; + return NULL; /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they are separated by underscores. So that I can use either form in my tests, and @@ -2347,62 +2358,74 @@ if (expciphers) uschar *s = expciphers; while (*s) { if (*s == '_') *s = '-'; s++; } DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); - if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers)) - return tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr); + if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers)) + { + tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr); + return NULL; + } } #ifdef SUPPORT_DANE if (tlsa_dnsa) { - SSL_CTX_set_verify(client_ctx, + SSL_CTX_set_verify(exim_client_ctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback_client_dane); if (!DANESSL_library_init()) - return tls_error(US"library init", host, NULL, errstr); - if (DANESSL_CTX_init(client_ctx) <= 0) - return tls_error(US"context init", host, NULL, errstr); + { + tls_error(US"library init", host, NULL, errstr); + return NULL; + } + if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0) + { + tls_error(US"context init", host, NULL, errstr); + return NULL; + } } else #endif - if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, - client_static_cbinfo, errstr)) != OK) - return rc; + if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob, + client_static_cbinfo, errstr) != OK) + return NULL; -if (!(client_ssl = SSL_new(client_ctx))) - return tls_error(US"SSL_new", host, NULL, errstr); -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 (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx))) + { + tls_error(US"SSL_new", host, NULL, errstr); + return NULL; + } +SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx)); +SSL_set_fd(exim_client_ctx->ssl, fd); +SSL_set_connect_state(exim_client_ctx->ssl); if (ob->tls_sni) { - if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni, errstr)) - return FAIL; - if (!tls_out.sni) + if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr)) + return NULL; + if (!tlsp->sni) { DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); } - else if (!Ustrlen(tls_out.sni)) - tls_out.sni = NULL; + else if (!Ustrlen(tlsp->sni)) + tlsp->sni = NULL; else { #ifdef EXIM_HAVE_OPENSSL_TLSEXT - DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni); - SSL_set_tlsext_host_name(client_ssl, tls_out.sni); + DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni); + SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni); #else log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n", - tls_out.sni); + tlsp->sni); #endif } } #ifdef SUPPORT_DANE if (tlsa_dnsa) - if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa, errstr)) != OK) - return rc; + if (dane_tlsa_load(exim_client_ctx->ssl, host, tlsa_dnsa, errstr) != OK) + return NULL; #endif #ifndef DISABLE_OCSP @@ -2427,9 +2450,9 @@ if (request_ocsp) if (request_ocsp) { - SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp); + SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp); client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp; - tls_out.ocsp = OCSP_NOT_RESP; + tlsp->ocsp = OCSP_NOT_RESP; } #endif @@ -2442,33 +2465,36 @@ client_static_cbinfo->event_action = tb->event_action; DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); sigalrm_seen = FALSE; alarm(ob->command_timeout); -rc = SSL_connect(client_ssl); +rc = SSL_connect(exim_client_ctx->ssl); alarm(0); #ifdef SUPPORT_DANE if (tlsa_dnsa) - DANESSL_cleanup(client_ssl); + DANESSL_cleanup(exim_client_ctx->ssl); #endif if (rc <= 0) - return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, - errstr); + { + tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr); + return NULL; + } DEBUG(D_tls) debug_printf("SSL_connect succeeded\n"); -peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn)); +peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn)); -construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits); -tls_out.cipher = cipherbuf; +construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits); +tlsp->cipher = cipherbuf; /* Record the certificate we presented */ { - X509 * crt = SSL_get_certificate(client_ssl); - tls_out.ourcert = crt ? X509_dup(crt) : NULL; + X509 * crt = SSL_get_certificate(exim_client_ctx->ssl); + tlsp->ourcert = crt ? X509_dup(crt) : NULL; } -tls_out.active = fd; -return OK; +tlsp->active.sock = fd; +tlsp->active.tls_ctx = exim_client_ctx; +return exim_client_ctx; } @@ -2503,53 +2529,55 @@ if (had_data_sigint) closed down, not that the socket itself has been closed down. Revert to non-SSL handling. */ -if (error == SSL_ERROR_ZERO_RETURN) +switch(error) { - DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); + case SSL_ERROR_NONE: + break; - receive_getc = smtp_getc; - receive_getbuf = smtp_getbuf; - receive_get_cache = smtp_get_cache; - receive_ungetc = smtp_ungetc; - receive_feof = smtp_feof; - receive_ferror = smtp_ferror; - receive_smtp_buffered = smtp_buffered; + case SSL_ERROR_ZERO_RETURN: + DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); - if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) - SSL_shutdown(server_ssl); + receive_getc = smtp_getc; + receive_getbuf = smtp_getbuf; + receive_get_cache = smtp_get_cache; + receive_ungetc = smtp_ungetc; + receive_feof = smtp_feof; + receive_ferror = smtp_ferror; + receive_smtp_buffered = smtp_buffered; -#ifndef DISABLE_OCSP - sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free); - server_static_cbinfo->verify_stack = NULL; -#endif - SSL_free(server_ssl); - SSL_CTX_free(server_ctx); - server_ctx = NULL; - server_ssl = NULL; - tls_in.active = -1; - tls_in.bits = 0; - tls_in.cipher = NULL; - tls_in.peerdn = NULL; - tls_in.sni = NULL; + if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) + SSL_shutdown(server_ssl); - return FALSE; - } +#ifndef DISABLE_OCSP + sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free); + server_static_cbinfo->verify_stack = NULL; +#endif + SSL_free(server_ssl); + SSL_CTX_free(server_ctx); + server_ctx = NULL; + server_ssl = NULL; + tls_in.active.sock = -1; + tls_in.active.tls_ctx = NULL; + tls_in.bits = 0; + tls_in.cipher = NULL; + tls_in.peerdn = NULL; + tls_in.sni = NULL; -/* Handle genuine errors */ + return FALSE; -else if (error == SSL_ERROR_SSL) - { - ERR_error_string(ERR_get_error(), ssl_errstring); - log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring); - ssl_xfer_error = TRUE; - return FALSE; - } + /* Handle genuine errors */ + case SSL_ERROR_SSL: + ERR_error_string(ERR_get_error(), ssl_errstring); + log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring); + ssl_xfer_error = TRUE; + return FALSE; -else if (error != SSL_ERROR_NONE) - { - DEBUG(D_tls) debug_printf("Got SSL error %d\n", error); - ssl_xfer_error = TRUE; - return FALSE; + default: + DEBUG(D_tls) debug_printf("Got SSL error %d\n", error); + DEBUG(D_tls) if (error == SSL_ERROR_SYSCALL) + debug_printf(" - syscall %s\n", strerror(errno)); + ssl_xfer_error = TRUE; + return FALSE; } #ifndef DISABLE_DKIM @@ -2633,6 +2661,7 @@ return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0; /* Arguments: + ct_ctx client context pointer, or NULL for the one global server context buff buffer of data len size of buffer @@ -2643,9 +2672,9 @@ Only used by the client-side TLS. */ int -tls_read(BOOL is_server, uschar *buff, size_t len) +tls_read(void * ct_ctx, uschar *buff, size_t len) { -SSL *ssl = is_server ? server_ssl : client_ssl; +SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; int inbytes; int error; @@ -2676,7 +2705,7 @@ return inbytes; /* Arguments: - is_server channel specifier + ct_ctx client context pointer, or NULL for the one global server context buff buffer of data len number of bytes more further data expected soon @@ -2688,10 +2717,10 @@ Used by both server-side and client-side TLS. */ int -tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more) +tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more) { int outbytes, error, left; -SSL *ssl = is_server ? server_ssl : client_ssl; +SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; static gstring * corked = NULL; DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__, @@ -2702,7 +2731,7 @@ DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__, one stream does it, in one context (i.e. no store reset). Currently it is used for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */ -if (is_server && (more || corked)) +if (!ct_ctx && (more || corked)) { corked = string_catn(corked, buff, len); if (more) @@ -2714,7 +2743,7 @@ if (is_server && (more || corked)) for (left = len; left > 0;) { - DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left); + DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left); outbytes = SSL_write(ssl, CS buff, left); error = SSL_get_error(ssl, outbytes); DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error); @@ -2759,6 +2788,7 @@ daemon, to shut down the TLS library, without actually doing a shutdown (which would tamper with the SSL session in the parent process). Arguments: + ct_ctx client TLS context pointer, or NULL for the one global server context shutdown 1 if TLS close-alert is to be sent, 2 if also response to be waited for @@ -2768,11 +2798,12 @@ Used by both server-side and client-side TLS. */ void -tls_close(BOOL is_server, int shutdown) +tls_close(void * ct_ctx, int shutdown) { -SSL_CTX **ctxp = is_server ? &server_ctx : &client_ctx; -SSL **sslp = is_server ? &server_ssl : &client_ssl; -int *fdp = is_server ? &tls_in.active : &tls_out.active; +exim_openssl_client_tls_ctx * o_ctx = ct_ctx; +SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx; +SSL **sslp = o_ctx ? &o_ctx->ssl : &server_ssl; +int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; if (*fdp < 0) return; /* TLS was not active */ @@ -2798,7 +2829,7 @@ if (shutdown) } #ifndef DISABLE_OCSP -if (is_server) +if (!o_ctx) /* server side */ { sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free); server_static_cbinfo->verify_stack = NULL; diff --git a/src/src/transport.c b/src/src/transport.c index a2da32159..d5ff7380b 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -242,7 +242,7 @@ for (i = 0; i < 100; i++) { rc = #ifdef SUPPORT_TLS - tls_out.active == fd ? tls_write(FALSE, block, len, more) : + tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) : #endif #ifdef MSG_MORE more && !(tctx->options & topt_not_socket) @@ -260,7 +260,7 @@ for (i = 0; i < 100; i++) rc = #ifdef SUPPORT_TLS - tls_out.active == fd ? tls_write(FALSE, block, len, more) : + tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) : #endif #ifdef MSG_MORE more && !(tctx->options & topt_not_socket) @@ -1075,7 +1075,7 @@ dkim signing, when we had CHUNKING input. */ if ( spool_file_wireformat && !(tctx->options & (topt_no_body | topt_end_dot)) && !nl_check_length - && tls_out.active != tctx->u.fd + && tls_out.active.sock != tctx->u.fd ) { ssize_t copied = 0; @@ -1877,12 +1877,12 @@ if (smtp_peer_options & OPTION_PIPE) argv[i++] = US"-MCP"; if (smtp_peer_options & OPTION_SIZE) argv[i++] = US"-MCS"; #ifdef SUPPORT_TLS if (smtp_peer_options & OPTION_TLS) - if (tls_out.active >= 0 || continue_proxy_cipher) + if (tls_out.active.sock >= 0 || continue_proxy_cipher) { argv[i++] = US"-MCt"; argv[i++] = sending_ip_address; argv[i++] = string_sprintf("%d", sending_port); - argv[i++] = tls_out.active >= 0 ? tls_out.cipher : continue_proxy_cipher; + argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher; } else argv[i++] = US"-MCT"; diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 6d7085881..1f0256f3d 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -1646,11 +1646,13 @@ if (!continue_hostname) /* Make the TCP connection */ - sx->inblock.sock = sx->outblock.sock = + sx->cctx.sock = smtp_connect(sx->host, sx->host_af, sx->interface, sx->ob->connect_timeout, sx->tblock); + sx->cctx.tls_ctx = NULL; + sx->inblock.cctx = sx->outblock.cctx = &sx->cctx; - if (sx->inblock.sock < 0) + if (sx->cctx.sock < 0) { uschar * msg = NULL; if (sx->verify) @@ -1703,7 +1705,7 @@ if (!continue_hostname) BOOL good_response; #ifdef TCP_QUICKACK - (void) setsockopt(sx->inblock.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); + (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); #endif good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', sx->ob->command_timeout); @@ -1882,16 +1884,18 @@ separate - we could match up by host ip+port as a bodge. */ else { - if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) + if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) { - sx->inblock.sock = sx->outblock.sock = cutthrough.fd; + sx->cctx = cutthrough.cctx; sx->host->port = sx->port = cutthrough.host.port; } else { - sx->inblock.sock = sx->outblock.sock = 0; /* stdin */ + sx->cctx.sock = 0; /* stdin */ + sx->cctx.tls_ctx = NULL; smtp_port_for_connect(sx->host, sx->port); /* Record the port that was used */ } + sx->inblock.cctx = sx->outblock.cctx = &sx->cctx; smtp_command = big_buffer; sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */ @@ -1899,7 +1903,8 @@ else held-open verify connection with TLS, nothing more to do. */ if ( continue_proxy_cipher - || (cutthrough.fd >= 0 && cutthrough.callout_hold_only && cutthrough.is_tls) + || (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only + && cutthrough.is_tls) ) { sx->peer_offered = smtp_peer_options; @@ -1959,18 +1964,19 @@ if ( smtp_peer_options & OPTION_TLS { address_item * addr; uschar * errstr; - int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock, + sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->host, + sx->addrlist, sx->tblock, # ifdef SUPPORT_DANE sx->dane ? &tlsa_dnsa : NULL, # endif - &errstr); + &tls_out, &errstr); - /* TLS negotiation failed; give an error. From outside, this function may - be called again to try in clear on a new connection, if the options permit - it for this host. */ - - if (rc != OK) + if (!sx->cctx.tls_ctx) { + /* TLS negotiation failed; give an error. From outside, this function may + be called again to try in clear on a new connection, if the options permit + it for this host. */ + # ifdef SUPPORT_DANE if (sx->dane) { @@ -2013,7 +2019,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_out.active >= 0) +if (tls_out.active.sock >= 0) { char *greeting_cmd; BOOL good_response; @@ -2091,7 +2097,7 @@ we skip this. */ if (continue_hostname == NULL #ifdef SUPPORT_TLS - || tls_out.active >= 0 + || tls_out.active.sock >= 0 #endif ) { @@ -2270,7 +2276,11 @@ if (sx->send_quit) (void)smtp_write_command(&sx->outblock, SCMD_FLUSH, "QUIT\r\n"); #ifdef SUPPORT_TLS -tls_close(FALSE, TLS_SHUTDOWN_NOWAIT); +if (sx->cctx.tls_ctx) + { + tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); + sx->cctx.tls_ctx = NULL; + } #endif /* Close the socket, and return the appropriate value, first setting @@ -2281,14 +2291,14 @@ remote_max_parallel is forced to 1 when delivering over an existing connection, HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); if (sx->send_quit) { - shutdown(sx->outblock.sock, SHUT_WR); - if (fcntl(sx->inblock.sock, F_SETFL, O_NONBLOCK) == 0) - for (rc = 16; read(sx->inblock.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;) + shutdown(sx->cctx.sock, SHUT_WR); + if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0) + for (rc = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;) rc--; /* drain socket */ sx->send_quit = FALSE; } -(void)close(sx->inblock.sock); -sx->inblock.sock = sx->outblock.sock = -1; +(void)close(sx->cctx.sock); +sx->cctx.sock = -1; #ifndef DISABLE_EVENT (void) event_raise(sx->tblock->event_action, US"tcp:close", NULL); @@ -2624,6 +2634,7 @@ Do blocking full-size writes, and reads under a timeout. Once both input channels are closed, exit the process. Arguments: + ct_ctx tls context buf space to use for buffering bufsiz size of buffer pfd pipe filedescriptor array; [0] is comms to proxied process @@ -2631,10 +2642,11 @@ Arguments: */ void -smtp_proxy_tls(uschar * buf, size_t bsize, int * pfd, int timeout) +smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd, + int timeout) { fd_set rfds, efds; -int max_fd = MAX(pfd[0], tls_out.active) + 1; +int max_fd = MAX(pfd[0], tls_out.active.sock) + 1; int rc, i, fd_bits, nbytes; close(pfd[1]); @@ -2647,7 +2659,7 @@ if ((rc = fork())) if (running_in_test_harness) millisleep(100); /* let parent debug out */ set_process_info("proxying TLS connection for continued transport"); FD_ZERO(&rfds); -FD_SET(tls_out.active, &rfds); +FD_SET(tls_out.active.sock, &rfds); FD_SET(pfd[0], &rfds); for (fd_bits = 3; fd_bits; ) @@ -2673,21 +2685,21 @@ for (fd_bits = 3; fd_bits; ) goto done; } - if (FD_ISSET(tls_out.active, &efds) || FD_ISSET(pfd[0], &efds)) + if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds)) { DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n", FD_ISSET(pfd[0], &efds) ? "proxy" : "tls"); goto done; } } - while (rc < 0 || !(FD_ISSET(tls_out.active, &rfds) || FD_ISSET(pfd[0], &rfds))); + while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds))); /* handle inbound data */ - if (FD_ISSET(tls_out.active, &rfds)) - if ((rc = tls_read(FALSE, buf, bsize)) <= 0) + if (FD_ISSET(tls_out.active.sock, &rfds)) + if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0) { fd_bits &= ~1; - FD_CLR(tls_out.active, &rfds); + FD_CLR(tls_out.active.sock, &rfds); shutdown(pfd[0], SHUT_WR); timeout = 5; } @@ -2697,19 +2709,19 @@ for (fd_bits = 3; fd_bits; ) if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done; } else if (fd_bits & 1) - FD_SET(tls_out.active, &rfds); + FD_SET(tls_out.active.sock, &rfds); /* handle outbound data */ if (FD_ISSET(pfd[0], &rfds)) if ((rc = read(pfd[0], buf, bsize)) <= 0) { fd_bits = 0; - tls_close(FALSE, TLS_SHUTDOWN_NOWAIT); + tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT); } else { for (nbytes = 0; rc - nbytes > 0; nbytes += i) - if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes, FALSE)) < 0) + if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0) goto done; } else if (fd_bits & 2) @@ -2952,7 +2964,7 @@ if (!(sx.peer_offered & OPTION_CHUNKING) && !sx.ok) else { transport_ctx tctx = { - {sx.inblock.sock}, + {sx.cctx.sock}, /*XXX will this need TLS info? */ tblock, addrlist, US".", US"..", /* Escaping strings */ @@ -3465,7 +3477,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit) || continue_more || ( #ifdef SUPPORT_TLS - ( tls_out.active < 0 && !continue_proxy_cipher + ( tls_out.active.sock < 0 && !continue_proxy_cipher || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK ) && @@ -3503,7 +3515,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit) if (sx.ok) { int pfd[2]; - int socket_fd = sx.inblock.sock; + int socket_fd = sx.cctx.sock; if (sx.first_addr != NULL) /* More addresses still to be sent */ @@ -3518,7 +3530,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit) the connection still open. */ #ifdef SUPPORT_TLS - if (tls_out.active >= 0) + if (tls_out.active.sock >= 0) if ( continue_more || verify_check_given_host(&sx.ob->hosts_noproxy_tls, host) == OK) { @@ -3528,7 +3540,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit) a new EHLO. If we don't get a good response, we don't attempt to pass the socket on. */ - tls_close(FALSE, TLS_SHUTDOWN_WAIT); + tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_WAIT); smtp_peer_options = smtp_peer_options_wrap; sx.ok = !sx.smtps && smtp_write_command(&sx.outblock, SCMD_FLUSH, @@ -3576,14 +3588,14 @@ propagate it from the initial get logging done asap. Which way to place the work makes assumptions about post-fork prioritisation which may not hold on all platforms. */ #ifdef SUPPORT_TLS - if (tls_out.active >= 0) + if (tls_out.active.sock >= 0) { int pid = fork(); if (pid == 0) /* child; fork again to disconnect totally */ { if (running_in_test_harness) millisleep(100); /* let parent debug out */ /* does not return */ - smtp_proxy_tls(sx.buffer, sizeof(sx.buffer), pfd, + smtp_proxy_tls(sx.cctx.tls_ctx, sx.buffer, sizeof(sx.buffer), pfd, sx.ob->command_timeout); } @@ -3593,8 +3605,10 @@ propagate it from the initial close(pfd[0]); /* tidy the inter-proc to disconn the proxy proc */ waitpid(pid, NULL, 0); - tls_close(FALSE, TLS_NO_SHUTDOWN); - (void)close(sx.inblock.sock); + tls_close(sx.cctx.tls_ctx, TLS_NO_SHUTDOWN); + sx.cctx.tls_ctx = NULL; + (void)close(sx.cctx.sock); + sx.cctx.sock = -1; continue_transport = NULL; continue_hostname = NULL; return yield; @@ -3639,7 +3653,7 @@ if (sx.send_quit) (void)smtp_write_command(&sx.outblock, SCMD_FLUSH, "QUIT\r\n") END_OFF: #ifdef SUPPORT_TLS -tls_close(FALSE, TLS_SHUTDOWN_NOWAIT); +tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); #endif /* Close the socket, and return the appropriate value, first setting @@ -3655,12 +3669,12 @@ case continue_more won't get set. */ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); if (sx.send_quit) { - shutdown(sx.outblock.sock, SHUT_WR); - if (fcntl(sx.inblock.sock, F_SETFL, O_NONBLOCK) == 0) - for (rc = 16; read(sx.inblock.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;) + shutdown(sx.cctx.sock, SHUT_WR); + if (fcntl(sx.cctx.sock, F_SETFL, O_NONBLOCK) == 0) + for (rc = 16; read(sx.cctx.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;) rc--; /* drain socket */ } -(void)close(sx.inblock.sock); +(void)close(sx.cctx.sock); #ifndef DISABLE_EVENT (void) event_raise(tblock->event_action, US"tcp:close", NULL); @@ -3696,19 +3710,24 @@ smtp_transport_closedown(transport_instance *tblock) { smtp_transport_options_block *ob = (smtp_transport_options_block *)tblock->options_block; +client_conn_ctx cctx; smtp_inblock inblock; smtp_outblock outblock; uschar buffer[256]; uschar inbuffer[4096]; uschar outbuffer[16]; -inblock.sock = fileno(stdin); +/*XXX really we need an active-smtp-client ctx, rather than assuming stdout */ +cctx.sock = fileno(stdin); +cctx.tls_ctx = cctx.sock == tls_out.active.sock ? tls_out.active.tls_ctx : NULL; + +inblock.cctx = &cctx; inblock.buffer = inbuffer; inblock.buffersize = sizeof(inbuffer); inblock.ptr = inbuffer; inblock.ptrend = inbuffer; -outblock.sock = inblock.sock; +outblock.cctx = &cctx; outblock.buffersize = sizeof(outbuffer); outblock.buffer = outbuffer; outblock.ptr = outbuffer; @@ -3718,7 +3737,7 @@ outblock.authenticating = FALSE; (void)smtp_write_command(&outblock, SCMD_FLUSH, "QUIT\r\n"); (void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2', ob->command_timeout); -(void)close(inblock.sock); +(void)close(cctx.sock); } @@ -3819,7 +3838,7 @@ DEBUG(D_transport) if (continue_hostname) debug_printf("already connected to %s [%s] (on fd %d)\n", continue_hostname, continue_host_address, - cutthrough.fd >= 0 ? cutthrough.fd : 0); + cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0); } /* Set the flag requesting that these hosts be added to the waiting @@ -4604,21 +4623,27 @@ retry_non_continued: if (continue_hostname && !continue_host_tried) { - int fd = cutthrough.fd >= 0 ? cutthrough.fd : 0; + int fd = cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0; DEBUG(D_transport) debug_printf("no hosts match already-open connection\n"); #ifdef SUPPORT_TLS - if (tls_out.active == fd) + /* A TLS conn could be open for a cutthrough, but not for a plain continued- + transport */ +/*XXX doublecheck that! */ + + if (cutthrough.cctx.sock >= 0 && cutthrough.is_tls) { - (void) tls_write(FALSE, US"QUIT\r\n", 6, FALSE); - tls_close(FALSE, TLS_SHUTDOWN_NOWAIT); + (void) tls_write(cutthrough.cctx.tls_ctx, US"QUIT\r\n", 6, FALSE); + tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); + cutthrough.cctx.tls_ctx = NULL; + cutthrough.is_tls = FALSE; } else #else (void) write(fd, US"QUIT\r\n", 6); #endif (void) close(fd); - cutthrough.fd = -1; + cutthrough.cctx.sock = -1; continue_hostname = NULL; goto retry_non_continued; } diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index 7727c0c6d..a33ef437f 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -146,8 +146,9 @@ typedef struct { address_item * next_addr; address_item * sync_addr; - smtp_inblock inblock; - smtp_outblock outblock; + client_conn_ctx cctx; + smtp_inblock inblock; + smtp_outblock outblock; uschar buffer[DELIVER_BUFFER_SIZE]; uschar inbuffer[4096]; uschar outbuffer[4096]; diff --git a/src/src/verify.c b/src/src/verify.c index 5d0551e89..c7c769a16 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -39,7 +39,7 @@ static tree_node *dnsbl_cache = NULL; #define MT_NOT 1 #define MT_ALL 2 -static uschar cutthrough_response(int, char, uschar **, int); +static uschar cutthrough_response(client_conn_ctx *, char, uschar **, int); @@ -410,10 +410,11 @@ if (addr->transport == cutthrough.addr.transport) /* Match! Send the RCPT TO, set done from the response */ done = - smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n", - transport_rcpt_address(addr, - addr->transport->rcpt_include_affixes)) >= 0 && - cutthrough_response(cutthrough.fd, '2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2'; + smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n", + transport_rcpt_address(addr, + addr->transport->rcpt_include_affixes)) >= 0 + && cutthrough_response(&cutthrough.cctx, '2', &resp, + CUTTHROUGH_DATA_TIMEOUT) == '2'; /* This would go horribly wrong if a callout fail was ignored by ACL. We punt by abandoning cutthrough on a reject, like the @@ -612,7 +613,7 @@ that conn for verification purposes (and later delivery also). Simplest coding means skipping this whole loop and doing the append separately. */ /* Can we re-use an open cutthrough connection? */ - if ( cutthrough.fd >= 0 + if ( cutthrough.cctx.sock >= 0 && (options & (vopt_callout_recipsender | vopt_callout_recippmaster)) == vopt_callout_recipsender && !random_local_part @@ -823,11 +824,11 @@ tls_retry_connection: debug_printf_indent("problem after random/rset/mfrom; reopen conn\n"); random_local_part = NULL; #ifdef SUPPORT_TLS - tls_close(FALSE, TLS_SHUTDOWN_NOWAIT); + tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); #endif HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); - (void)close(sx.inblock.sock); - sx.inblock.sock = sx.outblock.sock = -1; + (void)close(sx.cctx.sock); + sx.cctx.sock = -1; #ifndef DISABLE_EVENT (void) event_raise(addr->transport->event_action, US"tcp:close", NULL); @@ -1059,7 +1060,7 @@ no_conn: == vopt_callout_recipsender && !random_local_part && !pm_mailfrom - && cutthrough.fd < 0 + && cutthrough.cctx.sock < 0 && !sx.lmtp ) { @@ -1068,8 +1069,9 @@ no_conn: ? "cutthrough delivery" : "potential further verifies and delivery"); cutthrough.callout_hold_only = !cutthrough.delivery; - cutthrough.is_tls = tls_out.active >= 0; - cutthrough.fd = sx.outblock.sock; /* We assume no buffer in use in the outblock */ + cutthrough.is_tls = tls_out.active.sock >= 0; + /* We assume no buffer in use in the outblock */ + cutthrough.cctx = sx.cctx; cutthrough.nrcpt = 1; cutthrough.transport = addr->transport->name; cutthrough.interface = interface; @@ -1094,7 +1096,7 @@ no_conn: ctblock.buffersize = sizeof(ctbuffer); ctblock.ptr = ctbuffer; /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */ - ctblock.sock = cutthrough.fd; + ctblock.cctx = &cutthrough.cctx; } else { @@ -1110,14 +1112,18 @@ no_conn: '2', 1); } - if (sx.inblock.sock >= 0) + if (sx.cctx.sock >= 0) { #ifdef SUPPORT_TLS - tls_close(FALSE, TLS_SHUTDOWN_NOWAIT); + if (sx.cctx.tls_ctx) + { + tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); + sx.cctx.tls_ctx = NULL; + } #endif HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); - (void)close(sx.inblock.sock); - sx.inblock.sock = sx.outblock.sock = -1; + (void)close(sx.cctx.sock); + sx.cctx.sock = -1; #ifndef DISABLE_EVENT (void) event_raise(addr->transport->event_action, US"tcp:close", NULL); #endif @@ -1210,14 +1216,16 @@ return rc; static BOOL cutthrough_send(int n) { -if(cutthrough.fd < 0) +if(cutthrough.cctx.sock < 0) return TRUE; if( #ifdef SUPPORT_TLS - tls_out.active == cutthrough.fd ? tls_write(FALSE, ctblock.buffer, n, FALSE) : + cutthrough.is_tls + ? tls_write(cutthrough.cctx.tls_ctx, ctblock.buffer, n, FALSE) + : #endif - send(cutthrough.fd, ctblock.buffer, n, 0) > 0 + send(cutthrough.cctx.sock, ctblock.buffer, n, 0) > 0 ) { transport_count += n; @@ -1249,8 +1257,8 @@ return TRUE; static BOOL cutthrough_puts(uschar * cp, int n) { -if (cutthrough.fd < 0) return TRUE; -if (_cutthrough_puts(cp, n)) return TRUE; +if (cutthrough.cctx.sock < 0) return TRUE; +if (_cutthrough_puts(cp, n)) return TRUE; cancel_cutthrough_connection(TRUE, US"transmit failed"); return FALSE; } @@ -1301,7 +1309,7 @@ cutthrough_data_puts(US"\r\n", 2); /* Get and check response from cutthrough target */ static uschar -cutthrough_response(int fd, char expect, uschar ** copy, int timeout) +cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout) { smtp_inblock inblock; uschar inbuffer[4096]; @@ -1311,8 +1319,7 @@ inblock.buffer = inbuffer; inblock.buffersize = sizeof(inbuffer); inblock.ptr = inbuffer; inblock.ptrend = inbuffer; -inblock.sock = fd; -/* this relies on (inblock.sock == tls_out.active) */ +inblock.cctx = cctx; if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, timeout)) cancel_cutthrough_connection(TRUE, US"target timeout on read"); @@ -1334,7 +1341,7 @@ return responsebuffer[0]; BOOL cutthrough_predata(void) { -if(cutthrough.fd < 0 || cutthrough.callout_hold_only) +if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only) return FALSE; HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> DATA\n"); @@ -1342,7 +1349,7 @@ cutthrough_puts(US"DATA\r\n", 6); cutthrough_flush_send(); /* Assume nothing buffered. If it was it gets ignored. */ -return cutthrough_response(cutthrough.fd, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3'; +return cutthrough_response(&cutthrough.cctx, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3'; } @@ -1369,7 +1376,7 @@ cutthrough_headers_send(void) { transport_ctx tctx; -if(cutthrough.fd < 0 || cutthrough.callout_hold_only) +if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only) return FALSE; /* We share a routine with the mainline transport to handle header add/remove/rewrites, @@ -1377,7 +1384,7 @@ if(cutthrough.fd < 0 || cutthrough.callout_hold_only) */ HDEBUG(D_acl) debug_printf_indent("----------- start cutthrough headers send -----------\n"); -tctx.u.fd = cutthrough.fd; +tctx.u.fd = cutthrough.cctx.sock; tctx.tblock = cutthrough.addr.transport; tctx.addr = &cutthrough.addr; tctx.check_string = US"."; @@ -1396,25 +1403,31 @@ return TRUE; static void close_cutthrough_connection(const uschar * why) { -int fd = cutthrough.fd; +int fd = cutthrough.cctx.sock; if(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. */ + client_conn_ctx tmp_ctx = cutthrough.cctx; ctblock.ptr = ctbuffer; HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n"); _cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */ _cutthrough_flush_send(); - cutthrough.fd = -1; /* avoid recursion via read timeout */ + cutthrough.cctx.sock = -1; /* avoid recursion via read timeout */ cutthrough.nrcpt = 0; /* permit re-cutthrough on subsequent message */ /* Wait a short time for response, and discard it */ - cutthrough_response(fd, '2', NULL, 1); + cutthrough_response(&tmp_ctx, '2', NULL, 1); #ifdef SUPPORT_TLS - tls_close(FALSE, TLS_SHUTDOWN_NOWAIT); + if (cutthrough.is_tls) + { + tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); + cutthrough.cctx.tls_ctx = NULL; + cutthrough.is_tls = FALSE; + } #endif HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); (void)close(fd); @@ -1435,9 +1448,10 @@ cutthrough.delivery = cutthrough.callout_hold_only = FALSE; void release_cutthrough_connection(const uschar * why) { -if (cutthrough.fd < 0) return; +if (cutthrough.cctx.sock < 0) return; HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why); -cutthrough.fd = -1; +cutthrough.cctx.sock = -1; +cutthrough.cctx.tls_ctx = NULL; cutthrough.delivery = cutthrough.callout_hold_only = FALSE; } @@ -1463,7 +1477,8 @@ if( !cutthrough_puts(US".", 1) ) return cutthrough.addr.message; -res = cutthrough_response(cutthrough.fd, '2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT); +res = cutthrough_response(&cutthrough.cctx, '2', &cutthrough.addr.message, + CUTTHROUGH_DATA_TIMEOUT); for (addr = &cutthrough.addr; addr; addr = addr->next) { addr->message = cutthrough.addr.message; @@ -2681,7 +2696,8 @@ Side effect: any received ident value is put in sender_ident (NULL otherwise) void verify_get_ident(int port) { -int sock, host_af, qlen; +client_conn_ctx ident_conn_ctx = {0}; +int host_af, qlen; int received_sender_port, received_interface_port, n; uschar *p; blob early_data; @@ -2701,9 +2717,9 @@ to the incoming interface address. If the sender host address is an IPv6 address, the incoming interface address will also be IPv6. */ host_af = Ustrchr(sender_host_address, ':') == NULL ? AF_INET : AF_INET6; -if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return; +if ((ident_conn_ctx.sock = ip_socket(SOCK_STREAM, host_af)) < 0) return; -if (ip_bind(sock, host_af, interface_address, 0) < 0) +if (ip_bind(ident_conn_ctx.sock, host_af, interface_address, 0) < 0) { DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n", strerror(errno)); @@ -2717,7 +2733,7 @@ qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n", early_data.data = buffer; early_data.len = qlen; -if (ip_connect(sock, host_af, sender_host_address, port, +if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port, rfc1413_query_timeout, &early_data) < 0) { if (errno == ETIMEDOUT && LOGGING(ident_timeout)) @@ -2741,7 +2757,7 @@ for (;;) int size = sizeof(buffer) - (p - buffer); if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */ - count = ip_recv(sock, p, size, rfc1413_query_timeout); + count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout); if (count <= 0) goto END_OFF; /* Read error or EOF */ /* Scan what we just read, to see if we have reached the terminating \r\n. Be @@ -2806,7 +2822,7 @@ sender_ident = US string_printing(string_copyn(p, 127)); DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident); END_OFF: -(void)close(sock); +(void)close(ident_conn_ctx.sock); return; }