X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/f3ebb786e451da973560f1c9d8cdb151d25108b5..ee3c2fea18d0c940c2256c6bf041f546c703c375:/src/src/transport.c diff --git a/src/src/transport.c b/src/src/transport.c index ce02adbf2..31edb9692 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -3,6 +3,7 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 */ /* See the file NOTICE for conditions of use and distribution. */ /* General functions concerned with transportation, and generic options for all @@ -15,75 +16,76 @@ transports. */ data blocks and which therefore have the opt_public flag set. Note that there are other options living inside this structure which can be set only from certain transports. */ +#define LOFF(field) OPT_OFF(transport_instance, field) optionlist optionlist_transports[] = { /* name type value */ { "*expand_group", opt_stringptr|opt_hidden|opt_public, - (void *)offsetof(transport_instance, expand_gid) }, + LOFF(expand_gid) }, { "*expand_user", opt_stringptr|opt_hidden|opt_public, - (void *)offsetof(transport_instance, expand_uid) }, + LOFF(expand_uid) }, { "*headers_rewrite_flags", opt_int|opt_public|opt_hidden, - (void *)offsetof(transport_instance, rewrite_existflags) }, + LOFF(rewrite_existflags) }, { "*headers_rewrite_rules", opt_void|opt_public|opt_hidden, - (void *)offsetof(transport_instance, rewrite_rules) }, + LOFF(rewrite_rules) }, { "*set_group", opt_bool|opt_hidden|opt_public, - (void *)offsetof(transport_instance, gid_set) }, + LOFF(gid_set) }, { "*set_user", opt_bool|opt_hidden|opt_public, - (void *)offsetof(transport_instance, uid_set) }, + LOFF(uid_set) }, { "body_only", opt_bool|opt_public, - (void *)offsetof(transport_instance, body_only) }, + LOFF(body_only) }, { "current_directory", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, current_dir) }, + LOFF(current_dir) }, { "debug_print", opt_stringptr | opt_public, - (void *)offsetof(transport_instance, debug_string) }, + LOFF(debug_string) }, { "delivery_date_add", opt_bool|opt_public, - (void *)(offsetof(transport_instance, delivery_date_add)) }, + LOFF(delivery_date_add) }, { "disable_logging", opt_bool|opt_public, - (void *)(offsetof(transport_instance, disable_logging)) }, + LOFF(disable_logging) }, { "driver", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, driver_name) }, + LOFF(driver_name) }, { "envelope_to_add", opt_bool|opt_public, - (void *)(offsetof(transport_instance, envelope_to_add)) }, + LOFF(envelope_to_add) }, #ifndef DISABLE_EVENT { "event_action", opt_stringptr | opt_public, - (void *)offsetof(transport_instance, event_action) }, + LOFF(event_action) }, #endif { "group", opt_expand_gid|opt_public, - (void *)offsetof(transport_instance, gid) }, + LOFF(gid) }, { "headers_add", opt_stringptr|opt_public|opt_rep_str, - (void *)offsetof(transport_instance, add_headers) }, + LOFF(add_headers) }, { "headers_only", opt_bool|opt_public, - (void *)offsetof(transport_instance, headers_only) }, + LOFF(headers_only) }, { "headers_remove", opt_stringptr|opt_public|opt_rep_str, - (void *)offsetof(transport_instance, remove_headers) }, + LOFF(remove_headers) }, { "headers_rewrite", opt_rewrite|opt_public, - (void *)offsetof(transport_instance, headers_rewrite) }, + LOFF(headers_rewrite) }, { "home_directory", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, home_dir) }, + LOFF(home_dir) }, { "initgroups", opt_bool|opt_public, - (void *)offsetof(transport_instance, initgroups) }, + LOFF(initgroups) }, { "max_parallel", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, max_parallel) }, + LOFF(max_parallel) }, { "message_size_limit", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, message_size_limit) }, + LOFF(message_size_limit) }, { "rcpt_include_affixes", opt_bool|opt_public, - (void *)offsetof(transport_instance, rcpt_include_affixes) }, + LOFF(rcpt_include_affixes) }, { "retry_use_local_part", opt_bool|opt_public, - (void *)offsetof(transport_instance, retry_use_local_part) }, + LOFF(retry_use_local_part) }, { "return_path", opt_stringptr|opt_public, - (void *)(offsetof(transport_instance, return_path)) }, + LOFF(return_path) }, { "return_path_add", opt_bool|opt_public, - (void *)(offsetof(transport_instance, return_path_add)) }, + LOFF(return_path_add) }, { "shadow_condition", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, shadow_condition) }, + LOFF(shadow_condition) }, { "shadow_transport", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, shadow) }, + LOFF(shadow) }, { "transport_filter", opt_stringptr|opt_public, - (void *)offsetof(transport_instance, filter_command) }, + LOFF(filter_command) }, { "transport_filter_timeout", opt_time|opt_public, - (void *)offsetof(transport_instance, filter_timeout) }, + LOFF(filter_timeout) }, { "user", opt_expand_uid|opt_public, - (void *)offsetof(transport_instance, uid) } + LOFF(uid) } }; int optionlist_transports_size = nelem(optionlist_transports); @@ -172,6 +174,20 @@ for (transport_instance * t = transports; t; t = t->next) * Write block of data * *************************************************/ +static int +tpt_write(int fd, uschar * block, int len, BOOL more, int options) +{ +return +#ifndef DISABLE_TLS + tls_out.active.sock == fd + ? tls_write(tls_out.active.tls_ctx, block, len, more) : +#endif +#ifdef MSG_MORE + more && !(options & topt_not_socket) ? send(fd, block, len, MSG_MORE) : +#endif + write(fd, block, len); +} + /* Subroutine called by write_chunk() and at the end of the message actually to write a data block. Also called directly by some transports to write additional data to the file descriptor (e.g. prefix, suffix). @@ -215,10 +231,11 @@ Returns: TRUE on success, FALSE on failure (with errno preserved); */ static BOOL -transport_write_block_fd(transport_ctx * tctx, uschar *block, int len, BOOL more) +transport_write_block_fd(transport_ctx * tctx, uschar * block, int len, BOOL more) { int rc, save_errno; int local_timeout = transport_write_timeout; +int connretry = 1; int fd = tctx->u.fd; /* This loop is for handling incomplete writes and other retries. In most @@ -230,48 +247,43 @@ for (int i = 0; i < 100; i++) debug_printf("writing data block fd=%d size=%d timeout=%d%s\n", fd, len, local_timeout, more ? " (more expected)" : ""); - /* This code makes use of alarm() in order to implement the timeout. This - isn't a very tidy way of doing things. Using non-blocking I/O with select() - provides a neater approach. However, I don't know how to do this when TLS is - in use. */ + /* When doing TCP Fast Open we may get this far before the 3-way handshake + is complete, and write returns ENOTCONN. Detect that, wait for the socket + to become writable, and retry once only. */ - if (transport_write_timeout <= 0) /* No timeout wanted */ + for(;;) { - rc = -#ifndef DISABLE_TLS - 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) - ? send(fd, block, len, MSG_MORE) : -#endif - write(fd, block, len); - save_errno = errno; - } - - /* Timeout wanted. */ + fd_set fds; + /* This code makes use of alarm() in order to implement the timeout. This + isn't a very tidy way of doing things. Using non-blocking I/O with select() + provides a neater approach. However, I don't know how to do this when TLS is + in use. */ - else - { - ALARM(local_timeout); - - rc = -#ifndef DISABLE_TLS - 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) - ? send(fd, block, len, MSG_MORE) : -#endif - write(fd, block, len); - - save_errno = errno; - local_timeout = ALARM_CLR(0); - if (sigalrm_seen) + if (transport_write_timeout <= 0) /* No timeout wanted */ { - errno = ETIMEDOUT; - return FALSE; + rc = tpt_write(fd, block, len, more, tctx->options); + save_errno = errno; + } + else /* Timeout wanted. */ + { + sigalrm_seen = FALSE; + ALARM(local_timeout); + rc = tpt_write(fd, block, len, more, tctx->options); + save_errno = errno; + local_timeout = ALARM_CLR(0); + if (sigalrm_seen) + { + errno = ETIMEDOUT; + return FALSE; + } } + + if (rc >= 0 || errno != ENOTCONN || connretry <= 0) + break; + + FD_ZERO(&fds); FD_SET(fd, &fds); + select(fd+1, NULL, &fds, NULL, NULL); /* could set timout? */ + connretry--; } /* Hopefully, the most common case is success, so test that first. */ @@ -581,14 +593,14 @@ if (include_affixes) return addr->address; } -if (addr->suffix == NULL) +if (!addr->suffix) { - if (addr->prefix == NULL) return addr->address; + if (!addr->prefix) return addr->address; return addr->address + Ustrlen(addr->prefix); } at = Ustrrchr(addr->address, '@'); -plen = (addr->prefix == NULL)? 0 : Ustrlen(addr->prefix); +plen = addr->prefix ? Ustrlen(addr->prefix) : 0; slen = Ustrlen(addr->suffix); return string_sprintf("%.*s@%s", (int)(at - addr->address - plen - slen), @@ -728,10 +740,17 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old) return FALSE; } len = s ? Ustrlen(s) : 0; - if (strncmpic(h->text, s, len) != 0) continue; - ss = h->text + len; - while (*ss == ' ' || *ss == '\t') ss++; - if (*ss == ':') break; + if (len && s[len-1] == '*') /* trailing glob */ + { + if (strncmpic(h->text, s, len-1) == 0) break; + } + else + { + if (strncmpic(h->text, s, len) != 0) continue; + ss = h->text + len; + while (*ss == ' ' || *ss == '\t') ss++; + if (*ss == ':') break; + } } if (s) { include_header = FALSE; break; } } @@ -867,7 +886,7 @@ transport_write_timeout non-zero. Arguments: tctx - (fd, msg) Either and fd, to write the message to, + (fd, msg) Either an fd, to write the message to, or a string: if null write message to allocated space otherwire take content as headers. addr (chain of) addresses (for extra headers), or NULL; @@ -886,6 +905,7 @@ Arguments: add_delivery_date if TRUE, add a "delivery-date" header use_crlf if TRUE, turn NL into CR LF end_dot if TRUE, send a terminating "." line at the end + no_flush if TRUE, do not flush at end no_headers if TRUE, omit the headers no_body if TRUE, omit the body check_string a string to check for at the start of lines, or NULL @@ -1142,8 +1162,9 @@ if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2)) /* Write out any remaining data in the buffer before returning. */ -return (len = chunk_ptr - deliver_out_buffer) <= 0 || - transport_write_block(tctx, deliver_out_buffer, len, FALSE); +return (len = chunk_ptr - deliver_out_buffer) <= 0 + || transport_write_block(tctx, deliver_out_buffer, len, + !!(tctx->options & topt_no_flush)); } @@ -1171,7 +1192,8 @@ transport_write_message(transport_ctx * tctx, int size_limit) { BOOL last_filter_was_NL = TRUE; BOOL save_spool_file_wireformat = f.spool_file_wireformat; -int rc, len, yield, fd_read, fd_write, save_errno; +BOOL yield; +int rc, len, fd_read, fd_write, save_errno; int pfd[2] = {-1, -1}; pid_t filter_pid, write_pid; @@ -1215,10 +1237,10 @@ write_pid = (pid_t)(-1); { int bits = fcntl(tctx->u.fd, F_GETFD); - (void)fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC); + (void) fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC); filter_pid = child_open(USS transport_filter_argv, NULL, 077, - &fd_write, &fd_read, FALSE); - (void)fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC); + &fd_write, &fd_read, FALSE, US"transport-filter"); + (void) fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC); } if (filter_pid < 0) goto TIDY_UP; /* errno set */ @@ -1231,7 +1253,7 @@ via a(nother) pipe. While writing to the filter, we do not do the CRLF, smtp dots, or check string processing. */ if (pipe(pfd) != 0) goto TIDY_UP; /* errno set */ -if ((write_pid = fork()) == 0) +if ((write_pid = exim_fork(US"tpt-filter-writer")) == 0) { BOOL rc; (void)close(fd_read); @@ -1240,7 +1262,7 @@ if ((write_pid = fork()) == 0) tctx->u.fd = fd_write; tctx->check_string = tctx->escape_string = NULL; - tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat); + tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat | topt_no_flush); rc = internal_transport_write_message(tctx, size_limit); @@ -1251,11 +1273,11 @@ if ((write_pid = fork()) == 0) != sizeof(int) || write(pfd[pipe_write], (void *)&tctx->addr->more_errno, sizeof(int)) != sizeof(int) - || write(pfd[pipe_write], (void *)&tctx->addr->delivery_usec, sizeof(int)) - != sizeof(int) + || write(pfd[pipe_write], (void *)&tctx->addr->delivery_time, sizeof(struct timeval)) + != sizeof(struct timeval) ) rc = FALSE; /* compiler quietening */ - exim_underbar_exit(0); + exim_underbar_exit(EXIT_SUCCESS); } save_errno = errno; @@ -1275,7 +1297,7 @@ if (write_pid < 0) /* When testing, let the subprocess get going */ -if (f.running_in_test_harness) millisleep(250); +testharness_pause_ms(250); DEBUG(D_transport) debug_printf("process %d writing to transport filter\n", (int)write_pid); @@ -1303,6 +1325,7 @@ for (;;) ALARM_CLR(0); if (sigalrm_seen) { + DEBUG(D_transport) debug_printf("timed out reading from filter\n"); errno = ETIMEDOUT; f.transport_filter_timed_out = TRUE; goto TIDY_UP; @@ -1378,8 +1401,7 @@ if (write_pid > 0) { int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int)); dummy = read(pfd[pipe_read], (void *)&tctx->addr->more_errno, sizeof(int)); - dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_usec, sizeof(int)); - dummy = dummy; /* compiler quietening */ + dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_time, sizeof(struct timeval)); yield = FALSE; } } @@ -1421,7 +1443,7 @@ DEBUG(D_transport) { debug_printf("end of filtering transport writing: yield=%d\n", yield); if (!yield) - debug_printf("errno=%d more_errno=%d\n", errno, tctx->addr->more_errno); + debug_printf(" errno=%d more_errno=%d\n", errno, tctx->addr->more_errno); } return yield; @@ -1543,12 +1565,17 @@ for (host_item * host = hostlist; host; host = host->next) /* If this record is full, write it out with a new name constructed from the sequence number, increase the sequence number, and empty - the record. */ + the record. If we're doing a two-phase queue run initial phase, ping the + daemon to consider running a delivery on this host. */ if (host_record->count >= WAIT_NAME_MAX) { sprintf(CS buffer, "%.200s:%d", host->name, host_record->sequence); dbfn_write(dbm_file, buffer, host_record, sizeof(dbdata_wait) + host_length); +#ifndef DISABLE_QUEUE_RAMP + if (f.queue_2stage && queue_fast_ramp && !queue_run_in_order) + queue_notify_daemon(message_id); +#endif host_record->sequence++; host_record->count = 0; host_length = 0; @@ -1574,7 +1601,8 @@ for (host_item * host = hostlist; host; host = host->next) /* Update the database */ dbfn_write(dbm_file, host->name, host_record, sizeof(dbdata_wait) + host_length); - DEBUG(D_transport) debug_printf("added to list for %s\n", host->name); + DEBUG(D_transport) debug_printf("added %.*s to queue for %s\n", + MESSAGE_ID_LENGTH, message_id, host->name); } /* All now done */ @@ -1602,7 +1630,6 @@ Arguments: local_message_max maximum number of messages down one connection as set by the caller transport new_message_id set to the message id of a waiting message - more set TRUE if there are yet more messages waiting oicf_func function to call to validate if it is ok to send to this message_id from the current instance. oicf_data opaque data for oicf_func @@ -1618,7 +1645,7 @@ typedef struct msgq_s BOOL transport_check_waiting(const uschar *transport_name, const uschar *hostname, - int local_message_max, uschar *new_message_id, BOOL *more, oicf oicf_func, void *oicf_data) + int local_message_max, uschar *new_message_id, oicf oicf_func, void *oicf_data) { dbdata_wait *host_record; int host_length; @@ -1628,13 +1655,12 @@ open_db *dbm_file; int i; struct stat statbuf; -*more = FALSE; - DEBUG(D_transport) { debug_printf("transport_check_waiting entered\n"); debug_printf(" sequence=%d local_max=%d global_max=%d\n", continue_sequence, local_message_max, connection_max_messages); + acl_level++; } /* Do nothing if we have hit the maximum number that can be send down one @@ -1644,23 +1670,23 @@ if (connection_max_messages >= 0) local_message_max = connection_max_messages; if (local_message_max > 0 && continue_sequence >= local_message_max) { DEBUG(D_transport) - debug_printf("max messages for one connection reached: returning\n"); - return FALSE; + debug_printf_indent("max messages for one connection reached: returning\n"); + goto retfalse; } /* Open the waiting information database. */ if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name), O_RDWR, &dbblock, TRUE, TRUE))) - return FALSE; + goto retfalse; /* See if there is a record for this host; if not, there's nothing to do. */ if (!(host_record = dbfn_read(dbm_file, hostname))) { dbfn_close(dbm_file); - DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname); - return FALSE; + DEBUG(D_transport) debug_printf_indent("no messages waiting for %s\n", hostname); + goto retfalse; } /* If the data in the record looks corrupt, just log something and @@ -1671,7 +1697,7 @@ if (host_record->count > WAIT_NAME_MAX) dbfn_close(dbm_file); log_write(0, LOG_MAIN|LOG_PANIC, "smtp-wait database entry for %s has bad " "count=%d (max=%d)", hostname, host_record->count, WAIT_NAME_MAX); - return FALSE; + goto retfalse; } /* Scan the message ids in the record from the end towards the beginning, @@ -1709,6 +1735,7 @@ while (1) } /* first thing remove current message id if it exists */ + /*XXX but what if it has un-sent addrs? */ for (i = 0; i < msgq_count; ++i) if (Ustrcmp(msgq[i].message_id, message_id) == 0) @@ -1722,16 +1749,14 @@ while (1) for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep) { uschar subdir[2]; + uschar * mid = msgq[i].message_id; - subdir[0] = split_spool_directory ? msgq[i].message_id[5] : 0; - subdir[1] = 0; - - if (Ustat(spool_fname(US"input", subdir, msgq[i].message_id, US"-D"), - &statbuf) != 0) + set_subdir_str(subdir, mid, 0); + if (Ustat(spool_fname(US"input", subdir, mid, US"-D"), &statbuf) != 0) msgq[i].bKeep = FALSE; - else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data)) + else if (!oicf_func || oicf_func(mid, oicf_data)) { - Ustrcpy_nt(new_message_id, msgq[i].message_id); + Ustrcpy_nt(new_message_id, mid); msgq[i].bKeep = FALSE; bFound = TRUE; break; @@ -1810,8 +1835,8 @@ while (1) if (host_length <= 0) { dbfn_close(dbm_file); - DEBUG(D_transport) debug_printf("waiting messages already delivered\n"); - return FALSE; + DEBUG(D_transport) debug_printf_indent("waiting messages already delivered\n"); + goto retfalse; } /* we were not able to find an acceptable message, nor was there a @@ -1822,7 +1847,7 @@ while (1) { Ustrcpy(new_message_id, message_id); dbfn_close(dbm_file); - return FALSE; + goto retfalse; } } /* we need to process a continuation record */ @@ -1834,13 +1859,16 @@ record if required, close the database, and return TRUE. */ if (host_length > 0) { host_record->count = host_length/MESSAGE_ID_LENGTH; - dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length); - *more = TRUE; } dbfn_close(dbm_file); +DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: TRUE\n"); } return TRUE; + +retfalse: +DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: FALSE\n"); } +return FALSE; } /************************************************* @@ -1852,7 +1880,7 @@ void transport_do_pass_socket(const uschar *transport_name, const uschar *hostname, const uschar *hostaddress, uschar *id, int socket_fd) { -int i = 20; +int i = 22; const uschar **argv; /* Set up the calling arguments; use the standard function for the basics, @@ -1873,6 +1901,16 @@ if (smtp_peer_options & OPTION_TLS) argv[i++] = sending_ip_address; argv[i++] = string_sprintf("%d", sending_port); argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher; + + if (tls_out.sni) + { + argv[i++] = +#ifdef SUPPORT_DANE + tls_out.dane_verified ? US"-MCr" : +#endif + US"-MCs"; + argv[i++] = tls_out.sni; + } } else argv[i++] = US"-MCT"; @@ -1934,19 +1972,16 @@ int status; DEBUG(D_transport) debug_printf("transport_pass_socket entered\n"); -if ((pid = fork()) == 0) +if ((pid = exim_fork(US"continued-transport-interproc")) == 0) { /* Disconnect entirely from the parent process. If we are running in the test harness, wait for a bit to allow the previous process time to finish, write the log, etc., so that the output is always in the same order for automatic comparison. */ - if ((pid = fork()) != 0) - { - DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (final-pid %d)\n", pid); + if ((pid = exim_fork(US"continued-transport")) != 0) _exit(EXIT_SUCCESS); - } - if (f.running_in_test_harness) sleep(1); + testharness_pause_ms(1000); transport_do_pass_socket(transport_name, hostname, hostaddress, id, socket_fd); @@ -1960,7 +1995,6 @@ if (pid > 0) { int rc; while ((rc = wait(&status)) != pid && (rc >= 0 || errno != ECHILD)); - DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (inter-pid %d)\n", pid); return TRUE; } else